123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967 |
- // TODO: support EXTENDED request packets
- var TransformStream = require('stream').Transform;
- var ReadableStream = require('stream').Readable;
- var WritableStream = require('stream').Writable;
- var constants = require('fs').constants || process.binding('constants');
- var util = require('util');
- var inherits = util.inherits;
- var isDate = util.isDate;
- var listenerCount = require('events').EventEmitter.listenerCount;
- var fs = require('fs');
- var readString = require('./utils').readString;
- var readInt = require('./utils').readInt;
- var ATTR = {
- SIZE: 0x00000001,
- UIDGID: 0x00000002,
- PERMISSIONS: 0x00000004,
- ACMODTIME: 0x00000008,
- EXTENDED: 0x80000000
- };
- var STATUS_CODE = {
- OK: 0,
- EOF: 1,
- NO_SUCH_FILE: 2,
- PERMISSION_DENIED: 3,
- FAILURE: 4,
- BAD_MESSAGE: 5,
- NO_CONNECTION: 6,
- CONNECTION_LOST: 7,
- OP_UNSUPPORTED: 8
- };
- Object.keys(STATUS_CODE).forEach(function(key) {
- STATUS_CODE[STATUS_CODE[key]] = key;
- });
- var STATUS_CODE_STR = {
- 0: 'No error',
- 1: 'End of file',
- 2: 'No such file or directory',
- 3: 'Permission denied',
- 4: 'Failure',
- 5: 'Bad message',
- 6: 'No connection',
- 7: 'Connection lost',
- 8: 'Operation unsupported'
- };
- SFTPStream.STATUS_CODE = STATUS_CODE;
- var REQUEST = {
- INIT: 1,
- OPEN: 3,
- CLOSE: 4,
- READ: 5,
- WRITE: 6,
- LSTAT: 7,
- FSTAT: 8,
- SETSTAT: 9,
- FSETSTAT: 10,
- OPENDIR: 11,
- READDIR: 12,
- REMOVE: 13,
- MKDIR: 14,
- RMDIR: 15,
- REALPATH: 16,
- STAT: 17,
- RENAME: 18,
- READLINK: 19,
- SYMLINK: 20,
- EXTENDED: 200
- };
- Object.keys(REQUEST).forEach(function(key) {
- REQUEST[REQUEST[key]] = key;
- });
- var RESPONSE = {
- VERSION: 2,
- STATUS: 101,
- HANDLE: 102,
- DATA: 103,
- NAME: 104,
- ATTRS: 105,
- EXTENDED: 201
- };
- Object.keys(RESPONSE).forEach(function(key) {
- RESPONSE[RESPONSE[key]] = key;
- });
- var OPEN_MODE = {
- READ: 0x00000001,
- WRITE: 0x00000002,
- APPEND: 0x00000004,
- CREAT: 0x00000008,
- TRUNC: 0x00000010,
- EXCL: 0x00000020
- };
- SFTPStream.OPEN_MODE = OPEN_MODE;
- var MAX_PKT_LEN = 34000;
- var MAX_REQID = Math.pow(2, 32) - 1;
- var CLIENT_VERSION_BUFFER = new Buffer([0, 0, 0, 5 /* length */,
- REQUEST.INIT,
- 0, 0, 0, 3 /* version */]);
- var SERVER_VERSION_BUFFER = new Buffer([0, 0, 0, 5 /* length */,
- RESPONSE.VERSION,
- 0, 0, 0, 3 /* version */]);
- /*
- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02:
- The maximum size of a packet is in practice determined by the client
- (the maximum size of read or write requests that it sends, plus a few
- bytes of packet overhead). All servers SHOULD support packets of at
- least 34000 bytes (where the packet size refers to the full length,
- including the header above). This should allow for reads and writes
- of at most 32768 bytes.
- OpenSSH caps this to 256kb instead of the ~34kb as mentioned in the sftpv3
- spec.
- */
- var RE_OPENSSH = /^SSH-2.0-(?:OpenSSH|dropbear)/;
- var OPENSSH_MAX_DATA_LEN = (256 * 1024) - (2 * 1024)/*account for header data*/;
- function DEBUG_NOOP(msg) {}
- function SFTPStream(cfg, remoteIdentRaw) {
- if (typeof cfg === 'string' && !remoteIdentRaw) {
- remoteIdentRaw = cfg;
- cfg = undefined;
- }
- if (typeof cfg !== 'object' || !cfg)
- cfg = {};
- TransformStream.call(this, {
- highWaterMark: (typeof cfg.highWaterMark === 'number'
- ? cfg.highWaterMark
- : 32 * 1024)
- });
- this.debug = (typeof cfg.debug === 'function' ? cfg.debug : DEBUG_NOOP);
- this.server = (cfg.server ? true : false);
- this._isOpenSSH = (remoteIdentRaw && RE_OPENSSH.test(remoteIdentRaw));
- this._needContinue = false;
- this._state = {
- // common
- status: 'packet_header',
- writeReqid: -1,
- pktLeft: undefined,
- pktHdrBuf: new Buffer(9), // room for pktLen + pktType + req id
- pktBuf: undefined,
- pktType: undefined,
- version: undefined,
- extensions: {},
- // client
- maxDataLen: (this._isOpenSSH ? OPENSSH_MAX_DATA_LEN : 32768),
- requests: {}
- };
- var self = this;
- this.on('end', function() {
- self.readable = false;
- }).on('finish', onFinish)
- .on('prefinish', onFinish);
- function onFinish() {
- self.writable = false;
- self._cleanup(false);
- }
- if (!this.server)
- this.push(CLIENT_VERSION_BUFFER);
- }
- inherits(SFTPStream, TransformStream);
- SFTPStream.prototype.__read = TransformStream.prototype._read;
- SFTPStream.prototype._read = function(n) {
- if (this._needContinue) {
- this._needContinue = false;
- this.emit('continue');
- }
- return this.__read(n);
- };
- SFTPStream.prototype.__push = TransformStream.prototype.push;
- SFTPStream.prototype.push = function(chunk, encoding) {
- if (!this.readable)
- return false;
- if (chunk === null)
- this.readable = false;
- var ret = this.__push(chunk, encoding);
- this._needContinue = (ret === false);
- return ret;
- };
- SFTPStream.prototype._cleanup = function(callback) {
- var state = this._state;
- state.pktBuf = undefined; // give GC something to do
- var requests = state.requests;
- var keys = Object.keys(requests);
- var len = keys.length;
- if (len) {
- if (this.readable) {
- var err = new Error('SFTP session ended early');
- for (var i = 0, cb; i < len; ++i)
- (cb = requests[keys[i]].cb) && cb(err);
- }
- state.requests = {};
- }
- if (this.readable)
- this.push(null);
- if (!this._readableState.endEmitted && !this._readableState.flowing) {
- // Ugh!
- this.resume();
- }
- if (callback !== false) {
- this.debug('DEBUG[SFTP]: Parser: Malformed packet');
- callback && callback(new Error('Malformed packet'));
- }
- };
- SFTPStream.prototype._transform = function(chunk, encoding, callback) {
- var state = this._state;
- var server = this.server;
- var status = state.status;
- var pktType = state.pktType;
- var pktBuf = state.pktBuf;
- var pktLeft = state.pktLeft;
- var version = state.version;
- var pktHdrBuf = state.pktHdrBuf;
- var requests = state.requests;
- var debug = this.debug;
- var chunkLen = chunk.length;
- var chunkPos = 0;
- var buffer;
- var chunkLeft;
- var id;
- while (true) {
- if (status === 'discard') {
- chunkLeft = (chunkLen - chunkPos);
- if (pktLeft <= chunkLeft) {
- chunkPos += pktLeft;
- pktLeft = 0;
- status = 'packet_header';
- buffer = pktBuf = undefined;
- } else {
- pktLeft -= chunkLeft;
- break;
- }
- } else if (pktBuf !== undefined) {
- chunkLeft = (chunkLen - chunkPos);
- if (pktLeft <= chunkLeft) {
- chunk.copy(pktBuf,
- pktBuf.length - pktLeft,
- chunkPos,
- chunkPos + pktLeft);
- chunkPos += pktLeft;
- pktLeft = 0;
- buffer = pktBuf;
- pktBuf = undefined;
- continue;
- } else {
- chunk.copy(pktBuf, pktBuf.length - pktLeft, chunkPos);
- pktLeft -= chunkLeft;
- break;
- }
- } else if (status === 'packet_header') {
- if (!buffer) {
- pktLeft = 5;
- pktBuf = pktHdrBuf;
- } else {
- // here we read the right-most 5 bytes from buffer (pktHdrBuf)
- pktLeft = buffer.readUInt32BE(4, true) - 1; // account for type byte
- pktType = buffer[8];
- if (server) {
- if (version === undefined && pktType !== REQUEST.INIT) {
- debug('DEBUG[SFTP]: Parser: Unexpected packet before init');
- this._cleanup(false);
- return callback(new Error('Unexpected packet before init'));
- } else if (version !== undefined && pktType === REQUEST.INIT) {
- debug('DEBUG[SFTP]: Parser: Unexpected duplicate init');
- status = 'bad_pkt';
- } else if (pktLeft > MAX_PKT_LEN) {
- var msg = 'Packet length ('
- + pktLeft
- + ') exceeds max length ('
- + MAX_PKT_LEN
- + ')';
- debug('DEBUG[SFTP]: Parser: ' + msg);
- this._cleanup(false);
- return callback(new Error(msg));
- } else if (pktType === REQUEST.EXTENDED) {
- status = 'bad_pkt';
- } else if (REQUEST[pktType] === undefined) {
- debug('DEBUG[SFTP]: Parser: Unsupported packet type: ' + pktType);
- status = 'discard';
- }
- } else if (version === undefined && pktType !== RESPONSE.VERSION) {
- debug('DEBUG[SFTP]: Parser: Unexpected packet before version');
- this._cleanup(false);
- return callback(new Error('Unexpected packet before version'));
- } else if (version !== undefined && pktType === RESPONSE.VERSION) {
- debug('DEBUG[SFTP]: Parser: Unexpected duplicate version');
- status = 'bad_pkt';
- } else if (RESPONSE[pktType] === undefined) {
- status = 'discard';
- }
- if (status === 'bad_pkt') {
- // copy original packet info
- pktHdrBuf.writeUInt32BE(pktLeft, 0, true);
- pktHdrBuf[4] = pktType;
- pktLeft = 4;
- pktBuf = pktHdrBuf;
- } else {
- pktBuf = new Buffer(pktLeft);
- status = 'payload';
- }
- }
- } else if (status === 'payload') {
- if (pktType === RESPONSE.VERSION || pktType === REQUEST.INIT) {
- /*
- uint32 version
- <extension data>
- */
- version = state.version = readInt(buffer, 0, this, callback);
- if (version === false)
- return;
- if (version < 3) {
- this._cleanup(false);
- return callback(new Error('Incompatible SFTP version: ' + version));
- } else if (server)
- this.push(SERVER_VERSION_BUFFER);
- var buflen = buffer.length;
- var extname;
- var extdata;
- buffer._pos = 4;
- while (buffer._pos < buflen) {
- extname = readString(buffer, buffer._pos, 'ascii', this, callback);
- if (extname === false)
- return;
- extdata = readString(buffer, buffer._pos, 'ascii', this, callback);
- if (extdata === false)
- return;
- if (state.extensions[extname])
- state.extensions[extname].push(extdata);
- else
- state.extensions[extname] = [ extdata ];
- }
- this.emit('ready');
- } else {
- /*
- All other packets (client and server) begin with a (client) request
- id:
- uint32 id
- */
- id = readInt(buffer, 0, this, callback);
- if (id === false)
- return;
- var filename;
- var attrs;
- var handle;
- var data;
- if (!server) {
- var req = requests[id];
- var cb = req && req.cb;
- debug('DEBUG[SFTP]: Parser: Response: ' + RESPONSE[pktType]);
- if (req && cb) {
- if (pktType === RESPONSE.STATUS) {
- /*
- uint32 error/status code
- string error message (ISO-10646 UTF-8)
- string language tag
- */
- var code = readInt(buffer, 4, this, callback);
- if (code === false)
- return;
- if (code === STATUS_CODE.OK) {
- cb();
- } else {
- // We borrow OpenSSH behavior here, specifically we make the
- // message and language fields optional, despite the
- // specification requiring them (even if they are empty). This
- // helps to avoid problems with buggy implementations that do
- // not fully conform to the SFTP(v3) specification.
- var msg;
- var lang = '';
- if (buffer.length >= 12) {
- msg = readString(buffer, 8, 'utf8', this, callback);
- if (msg === false)
- return;
- if ((buffer._pos + 4) < buffer.length) {
- lang = readString(buffer,
- buffer._pos,
- 'ascii',
- this,
- callback);
- if (lang === false)
- return;
- }
- }
- var err = new Error(msg
- || STATUS_CODE_STR[code]
- || 'Unknown status');
- err.code = code;
- err.lang = lang;
- cb(err);
- }
- } else if (pktType === RESPONSE.HANDLE) {
- /*
- string handle
- */
- handle = readString(buffer, 4, this, callback);
- if (handle === false)
- return;
- cb(undefined, handle);
- } else if (pktType === RESPONSE.DATA) {
- /*
- string data
- */
- if (req.buffer) {
- // we have already pre-allocated space to store the data
- var dataLen = readInt(buffer, 4, this, callback);
- if (dataLen === false)
- return;
- var reqBufLen = req.buffer.length;
- if (dataLen > reqBufLen) {
- // truncate response data to fit expected size
- buffer.writeUInt32BE(reqBufLen, 4, true);
- }
- data = readString(buffer, 4, req.buffer, this, callback);
- if (data === false)
- return;
- cb(undefined, data, dataLen);
- } else {
- data = readString(buffer, 4, this, callback);
- if (data === false)
- return;
- cb(undefined, data);
- }
- } else if (pktType === RESPONSE.NAME) {
- /*
- uint32 count
- repeats count times:
- string filename
- string longname
- ATTRS attrs
- */
- var namesLen = readInt(buffer, 4, this, callback);
- if (namesLen === false)
- return;
- var names = [],
- longname;
- buffer._pos = 8;
- for (var i = 0; i < namesLen; ++i) {
- // we are going to assume UTF-8 for filenames despite the SFTPv3
- // spec not specifying an encoding because the specs for newer
- // versions of the protocol all explicitly specify UTF-8 for
- // filenames
- filename = readString(buffer,
- buffer._pos,
- 'utf8',
- this,
- callback);
- if (filename === false)
- return;
- // `longname` only exists in SFTPv3 and since it typically will
- // contain the filename, we assume it is also UTF-8
- longname = readString(buffer,
- buffer._pos,
- 'utf8',
- this,
- callback);
- if (longname === false)
- return;
- attrs = readAttrs(buffer, buffer._pos, this, callback);
- if (attrs === false)
- return;
- names.push({
- filename: filename,
- longname: longname,
- attrs: attrs
- });
- }
- cb(undefined, names);
- } else if (pktType === RESPONSE.ATTRS) {
- /*
- ATTRS attrs
- */
- attrs = readAttrs(buffer, 4, this, callback);
- if (attrs === false)
- return;
- cb(undefined, attrs);
- } else if (pktType === RESPONSE.EXTENDED) {
- if (req.extended) {
- switch (req.extended) {
- case 'statvfs@openssh.com':
- case 'fstatvfs@openssh.com':
- /*
- uint64 f_bsize // file system block size
- uint64 f_frsize // fundamental fs block size
- uint64 f_blocks // number of blocks (unit f_frsize)
- uint64 f_bfree // free blocks in file system
- uint64 f_bavail // free blocks for non-root
- uint64 f_files // total file inodes
- uint64 f_ffree // free file inodes
- uint64 f_favail // free file inodes for to non-root
- uint64 f_fsid // file system id
- uint64 f_flag // bit mask of f_flag values
- uint64 f_namemax // maximum filename length
- */
- var stats = {
- f_bsize: undefined,
- f_frsize: undefined,
- f_blocks: undefined,
- f_bfree: undefined,
- f_bavail: undefined,
- f_files: undefined,
- f_ffree: undefined,
- f_favail: undefined,
- f_sid: undefined,
- f_flag: undefined,
- f_namemax: undefined
- };
- stats.f_bsize = readUInt64BE(buffer, 4, this, callback);
- if (stats.f_bsize === false)
- return;
- stats.f_frsize = readUInt64BE(buffer, 12, this, callback);
- if (stats.f_frsize === false)
- return;
- stats.f_blocks = readUInt64BE(buffer, 20, this, callback);
- if (stats.f_blocks === false)
- return;
- stats.f_bfree = readUInt64BE(buffer, 28, this, callback);
- if (stats.f_bfree === false)
- return;
- stats.f_bavail = readUInt64BE(buffer, 36, this, callback);
- if (stats.f_bavail === false)
- return;
- stats.f_files = readUInt64BE(buffer, 44, this, callback);
- if (stats.f_files === false)
- return;
- stats.f_ffree = readUInt64BE(buffer, 52, this, callback);
- if (stats.f_ffree === false)
- return;
- stats.f_favail = readUInt64BE(buffer, 60, this, callback);
- if (stats.f_favail === false)
- return;
- stats.f_sid = readUInt64BE(buffer, 68, this, callback);
- if (stats.f_sid === false)
- return;
- stats.f_flag = readUInt64BE(buffer, 76, this, callback);
- if (stats.f_flag === false)
- return;
- stats.f_namemax = readUInt64BE(buffer, 84, this, callback);
- if (stats.f_namemax === false)
- return;
- cb(undefined, stats);
- break;
- }
- }
- // XXX: at least provide the raw buffer data to the callback in
- // case of unexpected extended response?
- cb();
- }
- }
- if (req)
- delete requests[id];
- } else {
- // server
- var evName = REQUEST[pktType];
- var offset;
- var path;
- debug('DEBUG[SFTP]: Parser: Request: ' + evName);
- if (listenerCount(this, evName)) {
- if (pktType === REQUEST.OPEN) {
- /*
- string filename
- uint32 pflags
- ATTRS attrs
- */
- filename = readString(buffer, 4, 'utf8', this, callback);
- if (filename === false)
- return;
- var pflags = readInt(buffer, buffer._pos, this, callback);
- if (pflags === false)
- return;
- attrs = readAttrs(buffer, buffer._pos + 4, this, callback);
- if (attrs === false)
- return;
- this.emit(evName, id, filename, pflags, attrs);
- } else if (pktType === REQUEST.CLOSE
- || pktType === REQUEST.FSTAT
- || pktType === REQUEST.READDIR) {
- /*
- string handle
- */
- handle = readString(buffer, 4, this, callback);
- if (handle === false)
- return;
- this.emit(evName, id, handle);
- } else if (pktType === REQUEST.READ) {
- /*
- string handle
- uint64 offset
- uint32 len
- */
- handle = readString(buffer, 4, this, callback);
- if (handle === false)
- return;
- offset = readUInt64BE(buffer, buffer._pos, this, callback);
- if (offset === false)
- return;
- var len = readInt(buffer, buffer._pos, this, callback);
- if (len === false)
- return;
- this.emit(evName, id, handle, offset, len);
- } else if (pktType === REQUEST.WRITE) {
- /*
- string handle
- uint64 offset
- string data
- */
- handle = readString(buffer, 4, this, callback);
- if (handle === false)
- return;
- offset = readUInt64BE(buffer, buffer._pos, this, callback);
- if (offset === false)
- return;
- data = readString(buffer, buffer._pos, this, callback);
- if (data === false)
- return;
- this.emit(evName, id, handle, offset, data);
- } else if (pktType === REQUEST.LSTAT
- || pktType === REQUEST.STAT
- || pktType === REQUEST.OPENDIR
- || pktType === REQUEST.REMOVE
- || pktType === REQUEST.RMDIR
- || pktType === REQUEST.REALPATH
- || pktType === REQUEST.READLINK) {
- /*
- string path
- */
- path = readString(buffer, 4, 'utf8', this, callback);
- if (path === false)
- return;
- this.emit(evName, id, path);
- } else if (pktType === REQUEST.SETSTAT
- || pktType === REQUEST.MKDIR) {
- /*
- string path
- ATTRS attrs
- */
- path = readString(buffer, 4, 'utf8', this, callback);
- if (path === false)
- return;
- attrs = readAttrs(buffer, buffer._pos, this, callback);
- if (attrs === false)
- return;
- this.emit(evName, id, path, attrs);
- } else if (pktType === REQUEST.FSETSTAT) {
- /*
- string handle
- ATTRS attrs
- */
- handle = readString(buffer, 4, this, callback);
- if (handle === false)
- return;
- attrs = readAttrs(buffer, buffer._pos, this, callback);
- if (attrs === false)
- return;
- this.emit(evName, id, handle, attrs);
- } else if (pktType === REQUEST.RENAME
- || pktType === REQUEST.SYMLINK) {
- /*
- RENAME:
- string oldpath
- string newpath
- SYMLINK:
- string linkpath
- string targetpath
- */
- var str1;
- var str2;
- str1 = readString(buffer, 4, 'utf8', this, callback);
- if (str1 === false)
- return;
- str2 = readString(buffer, buffer._pos, 'utf8', this, callback);
- if (str2 === false)
- return;
- if (pktType === REQUEST.SYMLINK && this._isOpenSSH) {
- // OpenSSH has linkpath and targetpath positions switched
- this.emit(evName, id, str2, str1);
- } else
- this.emit(evName, id, str1, str2);
- }
- } else {
- // automatically reject request if no handler for request type
- this.status(id, STATUS_CODE.OP_UNSUPPORTED);
- }
- }
- }
- // prepare for next packet
- status = 'packet_header';
- buffer = pktBuf = undefined;
- } else if (status === 'bad_pkt') {
- if (server && buffer[4] !== REQUEST.INIT) {
- var errCode = (buffer[4] === REQUEST.EXTENDED
- ? STATUS_CODE.OP_UNSUPPORTED
- : STATUS_CODE.FAILURE);
- // no request id for init/version packets, so we have no way to send a
- // status response, so we just close up shop ...
- if (buffer[4] === REQUEST.INIT || buffer[4] === RESPONSE.VERSION)
- return this._cleanup(callback);
- id = readInt(buffer, 5, this, callback);
- if (id === false)
- return;
- this.status(id, errCode);
- }
- // by this point we have already read the type byte and the id bytes, so
- // we subtract those from the number of bytes to skip
- pktLeft = buffer.readUInt32BE(0, true) - 5;
- status = 'discard';
- }
- if (chunkPos >= chunkLen)
- break;
- }
- state.status = status;
- state.pktType = pktType;
- state.pktBuf = pktBuf;
- state.pktLeft = pktLeft;
- state.version = version;
- callback();
- };
- // client
- SFTPStream.prototype.createReadStream = function(path, options) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- return new ReadStream(this, path, options);
- };
- SFTPStream.prototype.createWriteStream = function(path, options) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- return new WriteStream(this, path, options);
- };
- SFTPStream.prototype.open = function(path, flags_, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- if (typeof attrs === 'function') {
- cb = attrs;
- attrs = undefined;
- }
- var flags = stringToFlags(flags_);
- if (flags === null)
- throw new Error('Unknown flags string: ' + flags_);
- var attrFlags = 0;
- var attrBytes = 0;
- if (typeof attrs === 'string' || typeof attrs === 'number') {
- attrs = { mode: attrs };
- }
- if (typeof attrs === 'object') {
- attrs = attrsToBytes(attrs);
- attrFlags = attrs.flags;
- attrBytes = attrs.nbytes;
- attrs = attrs.bytes;
- }
- /*
- uint32 id
- string filename
- uint32 pflags
- ATTRS attrs
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen + 4 + 4 + attrBytes);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.OPEN;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- buf.writeUInt32BE(flags, p += pathlen, true);
- buf.writeUInt32BE(attrFlags, p += 4, true);
- if (attrs && attrFlags) {
- p += 4;
- for (var i = 0, len = attrs.length; i < len; ++i)
- for (var j = 0, len2 = attrs[i].length; j < len2; ++j)
- buf[p++] = attrs[i][j];
- }
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing OPEN');
- return this.push(buf);
- };
- SFTPStream.prototype.close = function(handle, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- var state = this._state;
- /*
- uint32 id
- string handle
- */
- var handlelen = handle.length;
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + handlelen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.CLOSE;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(handlelen, p, true);
- handle.copy(buf, p += 4);
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing CLOSE');
- return this.push(buf);
- };
- SFTPStream.prototype.readData = function(handle, buf, off, len, position, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- else if (!Buffer.isBuffer(buf))
- throw new Error('buffer is not a Buffer');
- else if (off >= buf.length)
- throw new Error('offset is out of bounds');
- else if (off + len > buf.length)
- throw new Error('length extends beyond buffer');
- else if (position === null)
- throw new Error('null position currently unsupported');
- var state = this._state;
- /*
- uint32 id
- string handle
- uint64 offset
- uint32 len
- */
- var handlelen = handle.length;
- var p = 9;
- var pos = position;
- var out = new Buffer(4 + 1 + 4 + 4 + handlelen + 8 + 4);
- out.writeUInt32BE(out.length - 4, 0, true);
- out[4] = REQUEST.READ;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- out.writeUInt32BE(reqid, 5, true);
- out.writeUInt32BE(handlelen, p, true);
- handle.copy(out, p += 4);
- p += handlelen;
- for (var i = 7; i >= 0; --i) {
- out[p + i] = pos & 0xFF;
- pos /= 256;
- }
- out.writeUInt32BE(len, p += 8, true);
- state.requests[reqid] = {
- cb: function(err, data, nb) {
- if (err && err.code !== STATUS_CODE.EOF)
- return cb(err);
- cb(undefined, nb || 0, data, position);
- },
- buffer: buf.slice(off, off + len)
- };
- this.debug('DEBUG[SFTP]: Outgoing: Writing READ');
- return this.push(out);
- };
- SFTPStream.prototype.writeData = function(handle, buf, off, len, position, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- else if (!Buffer.isBuffer(buf))
- throw new Error('buffer is not a Buffer');
- else if (off > buf.length)
- throw new Error('offset is out of bounds');
- else if (off + len > buf.length)
- throw new Error('length extends beyond buffer');
- else if (position === null)
- throw new Error('null position currently unsupported');
- var self = this;
- var state = this._state;
- if (!len) {
- cb && process.nextTick(function() { cb(undefined, 0); });
- return;
- }
- var overflow = (len > state.maxDataLen
- ? len - state.maxDataLen
- : 0);
- var origPosition = position;
- if (overflow)
- len = state.maxDataLen;
- /*
- uint32 id
- string handle
- uint64 offset
- string data
- */
- var handlelen = handle.length;
- var p = 9;
- var out = new Buffer(4 + 1 + 4 + 4 + handlelen + 8 + 4 + len);
- out.writeUInt32BE(out.length - 4, 0, true);
- out[4] = REQUEST.WRITE;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- out.writeUInt32BE(reqid, 5, true);
- out.writeUInt32BE(handlelen, p, true);
- handle.copy(out, p += 4);
- p += handlelen;
- for (var i = 7; i >= 0; --i) {
- out[p + i] = position & 0xFF;
- position /= 256;
- }
- out.writeUInt32BE(len, p += 8, true);
- buf.copy(out, p += 4, off, off + len);
- state.requests[reqid] = {
- cb: function(err) {
- if (err)
- cb && cb(err);
- else if (overflow) {
- self.writeData(handle,
- buf,
- off + len,
- overflow,
- origPosition + len,
- cb);
- } else
- cb && cb(undefined, off + len);
- }
- };
- this.debug('DEBUG[SFTP]: Outgoing: Writing WRITE');
- return this.push(out);
- };
- function tryCreateBuffer(size) {
- try {
- return new Buffer(size);
- } catch (ex) {
- return ex;
- }
- }
- function fastXfer(src, dst, srcPath, dstPath, opts, cb) {
- var concurrency = 64;
- var chunkSize = 32768;
- //var preserve = false;
- var onstep;
- var mode;
- if (typeof opts === 'function') {
- cb = opts;
- } else if (typeof opts === 'object') {
- if (typeof opts.concurrency === 'number'
- && opts.concurrency > 0
- && !isNaN(opts.concurrency))
- concurrency = opts.concurrency;
- if (typeof opts.chunkSize === 'number'
- && opts.chunkSize > 0
- && !isNaN(opts.chunkSize))
- chunkSize = opts.chunkSize;
- if (typeof opts.step === 'function')
- onstep = opts.step;
- //preserve = (opts.preserve ? true : false);
- if (typeof opts.mode === 'string' || typeof opts.mode === 'number')
- mode = modeNum(opts.mode);
- }
- // internal state variables
- var fsize;
- var chunk;
- var psrc = 0;
- var pdst = 0;
- var reads = 0;
- var total = 0;
- var hadError = false;
- var srcHandle;
- var dstHandle;
- var readbuf;
- var bufsize = chunkSize * concurrency;
- function onerror(err) {
- if (hadError)
- return;
- hadError = true;
- var left = 0;
- var cbfinal;
- if (srcHandle || dstHandle) {
- cbfinal = function() {
- if (--left === 0)
- cb(err);
- };
- if (srcHandle && (src === fs || src.writable))
- ++left;
- if (dstHandle && (dst === fs || dst.writable))
- ++left;
- if (srcHandle && (src === fs || src.writable))
- src.close(srcHandle, cbfinal);
- if (dstHandle && (dst === fs || dst.writable))
- dst.close(dstHandle, cbfinal);
- } else
- cb(err);
- }
- src.open(srcPath, 'r', function(err, sourceHandle) {
- if (err)
- return onerror(err);
- srcHandle = sourceHandle;
- src.fstat(srcHandle, function tryStat(err, attrs) {
- if (err) {
- if (src !== fs) {
- // Try stat() for sftp servers that may not support fstat() for
- // whatever reason
- src.stat(srcPath, function(err_, attrs_) {
- if (err_)
- return onerror(err);
- tryStat(null, attrs_);
- });
- return;
- }
- return onerror(err);
- }
- fsize = attrs.size;
- dst.open(dstPath, 'w', function(err, destHandle) {
- if (err)
- return onerror(err);
- dstHandle = destHandle;
- if (fsize <= 0)
- return onerror();
- // Use less memory where possible
- while (bufsize > fsize) {
- if (concurrency === 1) {
- bufsize = fsize;
- break;
- }
- bufsize -= chunkSize;
- --concurrency;
- }
- readbuf = tryCreateBuffer(bufsize);
- if (readbuf instanceof Error)
- return onerror(readbuf);
- if (mode !== undefined) {
- dst.fchmod(dstHandle, mode, function tryAgain(err) {
- if (err) {
- // Try chmod() for sftp servers that may not support fchmod() for
- // whatever reason
- dst.chmod(dstPath, mode, function(err_) {
- tryAgain();
- });
- return;
- }
- read();
- });
- } else {
- read();
- }
- function onread(err, nb, data, dstpos, datapos) {
- if (err)
- return onerror(err);
- if (src === fs)
- dst.writeData(dstHandle, data, datapos || 0, nb, dstpos, writeCb);
- else
- dst.write(dstHandle, data, datapos || 0, nb, dstpos, writeCb);
- function writeCb(err) {
- if (err)
- return onerror(err);
- total += nb;
- onstep && onstep(total, nb, fsize);
- if (--reads === 0) {
- if (total === fsize) {
- dst.close(dstHandle, function(err) {
- dstHandle = undefined;
- if (err)
- return onerror(err);
- src.close(srcHandle, function(err) {
- srcHandle = undefined;
- if (err)
- return onerror(err);
- cb();
- });
- });
- } else
- read();
- }
- }
- }
- function makeCb(psrc, pdst) {
- return function(err, nb, data) {
- onread(err, nb, data, pdst, psrc);
- };
- }
- function read() {
- while (pdst < fsize && reads < concurrency) {
- chunk = (pdst + chunkSize > fsize ? fsize - pdst : chunkSize);
- if (src === fs) {
- src.read(srcHandle,
- readbuf,
- psrc,
- chunk,
- pdst,
- makeCb(psrc, pdst));
- } else
- src.readData(srcHandle, readbuf, psrc, chunk, pdst, onread);
- psrc += chunk;
- pdst += chunk;
- ++reads;
- }
- psrc = 0;
- }
- });
- });
- });
- }
- SFTPStream.prototype.fastGet = function(remotePath, localPath, opts, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- fastXfer(this, fs, remotePath, localPath, opts, cb);
- };
- SFTPStream.prototype.fastPut = function(localPath, remotePath, opts, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- fastXfer(fs, this, localPath, remotePath, opts, cb);
- };
- SFTPStream.prototype.readFile = function(path, options, callback_) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var callback;
- if (typeof callback_ === 'function') {
- callback = callback_;
- } else if (typeof options === 'function') {
- callback = options;
- options = undefined;
- }
- var self = this;
- if (typeof options === 'string')
- options = { encoding: options, flag: 'r' };
- else if (!options)
- options = { encoding: null, flag: 'r' };
- else if (typeof options !== 'object')
- throw new TypeError('Bad arguments');
- var encoding = options.encoding;
- if (encoding && !Buffer.isEncoding(encoding))
- throw new Error('Unknown encoding: ' + encoding);
- // first, stat the file, so we know the size.
- var size;
- var buffer; // single buffer with file data
- var buffers; // list for when size is unknown
- var pos = 0;
- var handle;
- // SFTPv3 does not support using -1 for read position, so we have to track
- // read position manually
- var bytesRead = 0;
- var flag = options.flag || 'r';
- this.open(path, flag, 438 /*=0666*/, function(er, handle_) {
- if (er)
- return callback && callback(er);
- handle = handle_;
- self.fstat(handle, function tryStat(er, st) {
- if (er) {
- // Try stat() for sftp servers that may not support fstat() for
- // whatever reason
- self.stat(path, function(er_, st_) {
- if (er_) {
- return self.close(handle, function() {
- callback && callback(er);
- });
- }
- tryStat(null, st_);
- });
- return;
- }
- size = st.size || 0;
- if (size === 0) {
- // the kernel lies about many files.
- // Go ahead and try to read some bytes.
- buffers = [];
- return read();
- }
- buffer = new Buffer(size);
- read();
- });
- });
- function read() {
- if (size === 0) {
- buffer = new Buffer(8192);
- self.readData(handle, buffer, 0, 8192, bytesRead, afterRead);
- } else
- self.readData(handle, buffer, pos, size - pos, bytesRead, afterRead);
- }
- function afterRead(er, nbytes) {
- if (er) {
- return self.close(handle, function() {
- return callback && callback(er);
- });
- }
- if (nbytes === 0)
- return close();
- bytesRead += nbytes;
- pos += nbytes;
- if (size !== 0) {
- if (pos === size)
- close();
- else
- read();
- } else {
- // unknown size, just read until we don't get bytes.
- buffers.push(buffer.slice(0, nbytes));
- read();
- }
- }
- function close() {
- self.close(handle, function(er) {
- if (size === 0) {
- // collected the data into the buffers list.
- buffer = Buffer.concat(buffers, pos);
- } else if (pos < size)
- buffer = buffer.slice(0, pos);
- if (encoding)
- buffer = buffer.toString(encoding);
- return callback && callback(er, buffer);
- });
- }
- };
- function writeAll(self, handle, buffer, offset, length, position, callback_) {
- var callback = (typeof callback_ === 'function' ? callback_ : undefined);
- self.writeData(handle,
- buffer,
- offset,
- length,
- position,
- function(writeErr, written) {
- if (writeErr) {
- return self.close(handle, function() {
- callback && callback(writeErr);
- });
- }
- if (written === length)
- self.close(handle, callback);
- else {
- offset += written;
- length -= written;
- position += written;
- writeAll(self, handle, buffer, offset, length, position, callback);
- }
- });
- }
- SFTPStream.prototype.writeFile = function(path, data, options, callback_) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var callback;
- if (typeof callback_ === 'function') {
- callback = callback_;
- } else if (typeof options === 'function') {
- callback = options;
- options = undefined;
- }
- var self = this;
- if (typeof options === 'string')
- options = { encoding: options, mode: 438, flag: 'w' };
- else if (!options)
- options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'w' };
- else if (typeof options !== 'object')
- throw new TypeError('Bad arguments');
- if (options.encoding && !Buffer.isEncoding(options.encoding))
- throw new Error('Unknown encoding: ' + options.encoding);
- var flag = options.flag || 'w';
- this.open(path, flag, options.mode, function(openErr, handle) {
- if (openErr)
- callback && callback(openErr);
- else {
- var buffer = (Buffer.isBuffer(data)
- ? data
- : new Buffer('' + data, options.encoding || 'utf8'));
- var position = (/a/.test(flag) ? null : 0);
- // SFTPv3 does not support the notion of 'current position'
- // (null position), so we just attempt to append to the end of the file
- // instead
- if (position === null) {
- self.fstat(handle, function tryStat(er, st) {
- if (er) {
- // Try stat() for sftp servers that may not support fstat() for
- // whatever reason
- self.stat(path, function(er_, st_) {
- if (er_) {
- return self.close(handle, function() {
- callback && callback(er);
- });
- }
- tryStat(null, st_);
- });
- return;
- }
- writeAll(self, handle, buffer, 0, buffer.length, st.size, callback);
- });
- return;
- }
- writeAll(self, handle, buffer, 0, buffer.length, position, callback);
- }
- });
- };
- SFTPStream.prototype.appendFile = function(path, data, options, callback_) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var callback;
- if (typeof callback_ === 'function') {
- callback = callback_;
- } else if (typeof options === 'function') {
- callback = options;
- options = undefined;
- }
- if (typeof options === 'string')
- options = { encoding: options, mode: 438, flag: 'a' };
- else if (!options)
- options = { encoding: 'utf8', mode: 438 /*=0666*/, flag: 'a' };
- else if (typeof options !== 'object')
- throw new TypeError('Bad arguments');
- if (!options.flag)
- options = util._extend({ flag: 'a' }, options);
- this.writeFile(path, data, options, callback);
- };
- SFTPStream.prototype.exists = function(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- this.stat(path, function(err) {
- cb && cb(err ? false : true);
- });
- };
- SFTPStream.prototype.unlink = function(filename, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string filename
- */
- var fnamelen = Buffer.byteLength(filename);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + fnamelen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.REMOVE;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(fnamelen, p, true);
- buf.write(filename, p += 4, fnamelen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing REMOVE');
- return this.push(buf);
- };
- SFTPStream.prototype.rename = function(oldPath, newPath, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string oldpath
- string newpath
- */
- var oldlen = Buffer.byteLength(oldPath);
- var newlen = Buffer.byteLength(newPath);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + oldlen + 4 + newlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.RENAME;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(oldlen, p, true);
- buf.write(oldPath, p += 4, oldlen, 'utf8');
- buf.writeUInt32BE(newlen, p += oldlen, true);
- buf.write(newPath, p += 4, newlen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing RENAME');
- return this.push(buf);
- };
- SFTPStream.prototype.mkdir = function(path, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var flags = 0;
- var attrBytes = 0;
- var state = this._state;
- if (typeof attrs === 'function') {
- cb = attrs;
- attrs = undefined;
- }
- if (typeof attrs === 'object') {
- attrs = attrsToBytes(attrs);
- flags = attrs.flags;
- attrBytes = attrs.nbytes;
- attrs = attrs.bytes;
- }
- /*
- uint32 id
- string path
- ATTRS attrs
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen + 4 + attrBytes);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.MKDIR;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- buf.writeUInt32BE(flags, p += pathlen);
- if (flags) {
- p += 4;
- for (var i = 0, len = attrs.length; i < len; ++i)
- for (var j = 0, len2 = attrs[i].length; j < len2; ++j)
- buf[p++] = attrs[i][j];
- }
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing MKDIR');
- return this.push(buf);
- };
- SFTPStream.prototype.rmdir = function(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string path
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.RMDIR;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing RMDIR');
- return this.push(buf);
- };
- SFTPStream.prototype.readdir = function(where, opts, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- var doFilter;
- if (typeof opts === 'function') {
- cb = opts;
- opts = {};
- }
- if (typeof opts !== 'object')
- opts = {};
- doFilter = (opts && opts.full ? false : true);
- if (!Buffer.isBuffer(where) && typeof where !== 'string')
- throw new Error('missing directory handle or path');
- if (typeof where === 'string') {
- var self = this;
- var entries = [];
- var e = 0;
- return this.opendir(where, function reread(err, handle) {
- if (err)
- return cb(err);
- self.readdir(handle, opts, function(err, list) {
- var eof = (err && err.code === STATUS_CODE.EOF);
- if (err && !eof) {
- return self.close(handle, function() {
- cb(err);
- });
- } else if (eof) {
- return self.close(handle, function(err) {
- if (err)
- return cb(err);
- cb(undefined, entries);
- });
- }
- for (var i = 0, len = list.length; i < len; ++i, ++e)
- entries[e] = list[i];
- reread(undefined, handle);
- });
- });
- }
- /*
- uint32 id
- string handle
- */
- var handlelen = where.length;
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + handlelen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.READDIR;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(handlelen, p, true);
- where.copy(buf, p += 4);
- state.requests[reqid] = {
- cb: (doFilter
- ? function(err, list) {
- if (err)
- return cb(err);
- for (var i = list.length - 1; i >= 0; --i) {
- if (list[i].filename === '.' || list[i].filename === '..')
- list.splice(i, 1);
- }
- cb(undefined, list);
- }
- : cb)
- };
- this.debug('DEBUG[SFTP]: Outgoing: Writing READDIR');
- return this.push(buf);
- };
- SFTPStream.prototype.fstat = function(handle, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- var state = this._state;
- /*
- uint32 id
- string handle
- */
- var handlelen = handle.length;
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + handlelen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.FSTAT;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(handlelen, p, true);
- handle.copy(buf, p += 4);
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing FSTAT');
- return this.push(buf);
- };
- SFTPStream.prototype.stat = function(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string path
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.STAT;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing STAT');
- return this.push(buf);
- };
- SFTPStream.prototype.lstat = function(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string path
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.LSTAT;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing LSTAT');
- return this.push(buf);
- };
- SFTPStream.prototype.opendir = function(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string path
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.OPENDIR;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing OPENDIR');
- return this.push(buf);
- };
- SFTPStream.prototype.setstat = function(path, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var flags = 0;
- var attrBytes = 0;
- var state = this._state;
- if (typeof attrs === 'object') {
- attrs = attrsToBytes(attrs);
- flags = attrs.flags;
- attrBytes = attrs.nbytes;
- attrs = attrs.bytes;
- } else if (typeof attrs === 'function')
- cb = attrs;
- /*
- uint32 id
- string path
- ATTRS attrs
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen + 4 + attrBytes);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.SETSTAT;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- buf.writeUInt32BE(flags, p += pathlen);
- if (flags) {
- p += 4;
- for (var i = 0, len = attrs.length; i < len; ++i)
- for (var j = 0, len2 = attrs[i].length; j < len2; ++j)
- buf[p++] = attrs[i][j];
- }
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing SETSTAT');
- return this.push(buf);
- };
- SFTPStream.prototype.fsetstat = function(handle, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- var flags = 0;
- var attrBytes = 0;
- var state = this._state;
- if (typeof attrs === 'object') {
- attrs = attrsToBytes(attrs);
- flags = attrs.flags;
- attrBytes = attrs.nbytes;
- attrs = attrs.bytes;
- } else if (typeof attrs === 'function')
- cb = attrs;
- /*
- uint32 id
- string handle
- ATTRS attrs
- */
- var handlelen = handle.length;
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + handlelen + 4 + attrBytes);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.FSETSTAT;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(handlelen, p, true);
- handle.copy(buf, p += 4);
- buf.writeUInt32BE(flags, p += handlelen);
- if (flags) {
- p += 4;
- for (var i = 0, len = attrs.length; i < len; ++i)
- for (var j = 0, len2 = attrs[i].length; j < len2; ++j)
- buf[p++] = attrs[i][j];
- }
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing FSETSTAT');
- return this.push(buf);
- };
- SFTPStream.prototype.futimes = function(handle, atime, mtime, cb) {
- return this.fsetstat(handle, {
- atime: toUnixTimestamp(atime),
- mtime: toUnixTimestamp(mtime)
- }, cb);
- };
- SFTPStream.prototype.utimes = function(path, atime, mtime, cb) {
- return this.setstat(path, {
- atime: toUnixTimestamp(atime),
- mtime: toUnixTimestamp(mtime)
- }, cb);
- };
- SFTPStream.prototype.fchown = function(handle, uid, gid, cb) {
- return this.fsetstat(handle, {
- uid: uid,
- gid: gid
- }, cb);
- };
- SFTPStream.prototype.chown = function(path, uid, gid, cb) {
- return this.setstat(path, {
- uid: uid,
- gid: gid
- }, cb);
- };
- SFTPStream.prototype.fchmod = function(handle, mode, cb) {
- return this.fsetstat(handle, {
- mode: mode
- }, cb);
- };
- SFTPStream.prototype.chmod = function(path, mode, cb) {
- return this.setstat(path, {
- mode: mode
- }, cb);
- };
- SFTPStream.prototype.readlink = function(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string path
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.READLINK;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- state.requests[reqid] = {
- cb: function(err, names) {
- if (err)
- return cb(err);
- else if (!names || !names.length)
- return cb(new Error('Response missing link info'));
- cb(undefined, names[0].filename);
- }
- };
- this.debug('DEBUG[SFTP]: Outgoing: Writing READLINK');
- return this.push(buf);
- };
- SFTPStream.prototype.symlink = function(targetPath, linkPath, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string linkpath
- string targetpath
- */
- var linklen = Buffer.byteLength(linkPath);
- var targetlen = Buffer.byteLength(targetPath);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + linklen + 4 + targetlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.SYMLINK;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- if (this._isOpenSSH) {
- // OpenSSH has linkpath and targetpath positions switched
- buf.writeUInt32BE(targetlen, p, true);
- buf.write(targetPath, p += 4, targetlen, 'utf8');
- buf.writeUInt32BE(linklen, p += targetlen, true);
- buf.write(linkPath, p += 4, linklen, 'utf8');
- } else {
- buf.writeUInt32BE(linklen, p, true);
- buf.write(linkPath, p += 4, linklen, 'utf8');
- buf.writeUInt32BE(targetlen, p += linklen, true);
- buf.write(targetPath, p += 4, targetlen, 'utf8');
- }
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing SYMLINK');
- return this.push(buf);
- };
- SFTPStream.prototype.realpath = function(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- var state = this._state;
- /*
- uint32 id
- string path
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + pathlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.REALPATH;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(pathlen, p, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- state.requests[reqid] = {
- cb: function(err, names) {
- if (err)
- return cb(err);
- else if (!names || !names.length)
- return cb(new Error('Response missing path info'));
- cb(undefined, names[0].filename);
- }
- };
- this.debug('DEBUG[SFTP]: Outgoing: Writing REALPATH');
- return this.push(buf);
- };
- // extended requests
- SFTPStream.prototype.ext_openssh_rename = function(oldPath, newPath, cb) {
- var state = this._state;
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!state.extensions['posix-rename@openssh.com']
- || state.extensions['posix-rename@openssh.com'].indexOf('1') === -1)
- throw new Error('Server does not support this extended request');
- /*
- uint32 id
- string "posix-rename@openssh.com"
- string oldpath
- string newpath
- */
- var oldlen = Buffer.byteLength(oldPath);
- var newlen = Buffer.byteLength(newPath);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + 24 + 4 + oldlen + 4 + newlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.EXTENDED;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(24, p, true);
- buf.write('posix-rename@openssh.com', p += 4, 24, 'ascii');
- buf.writeUInt32BE(oldlen, p += 24, true);
- buf.write(oldPath, p += 4, oldlen, 'utf8');
- buf.writeUInt32BE(newlen, p += oldlen, true);
- buf.write(newPath, p += 4, newlen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing posix-rename@openssh.com');
- return this.push(buf);
- };
- SFTPStream.prototype.ext_openssh_statvfs = function(path, cb) {
- var state = this._state;
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!state.extensions['statvfs@openssh.com']
- || state.extensions['statvfs@openssh.com'].indexOf('2') === -1)
- throw new Error('Server does not support this extended request');
- /*
- uint32 id
- string "statvfs@openssh.com"
- string path
- */
- var pathlen = Buffer.byteLength(path);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + 19 + 4 + pathlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.EXTENDED;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(19, p, true);
- buf.write('statvfs@openssh.com', p += 4, 19, 'ascii');
- buf.writeUInt32BE(pathlen, p += 19, true);
- buf.write(path, p += 4, pathlen, 'utf8');
- state.requests[reqid] = {
- extended: 'statvfs@openssh.com',
- cb: cb
- };
- this.debug('DEBUG[SFTP]: Outgoing: Writing statvfs@openssh.com');
- return this.push(buf);
- };
- SFTPStream.prototype.ext_openssh_fstatvfs = function(handle, cb) {
- var state = this._state;
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!state.extensions['fstatvfs@openssh.com']
- || state.extensions['fstatvfs@openssh.com'].indexOf('2') === -1)
- throw new Error('Server does not support this extended request');
- else if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- /*
- uint32 id
- string "fstatvfs@openssh.com"
- string handle
- */
- var handlelen = handle.length;
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + 20 + 4 + handlelen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.EXTENDED;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(20, p, true);
- buf.write('fstatvfs@openssh.com', p += 4, 20, 'ascii');
- buf.writeUInt32BE(handlelen, p += 20, true);
- buf.write(handle, p += 4, handlelen, 'utf8');
- state.requests[reqid] = {
- extended: 'fstatvfs@openssh.com',
- cb: cb
- };
- this.debug('DEBUG[SFTP]: Outgoing: Writing fstatvfs@openssh.com');
- return this.push(buf);
- };
- SFTPStream.prototype.ext_openssh_hardlink = function(oldPath, newPath, cb) {
- var state = this._state;
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!state.extensions['hardlink@openssh.com']
- || state.extensions['hardlink@openssh.com'].indexOf('1') === -1)
- throw new Error('Server does not support this extended request');
- /*
- uint32 id
- string "hardlink@openssh.com"
- string oldpath
- string newpath
- */
- var oldlen = Buffer.byteLength(oldPath);
- var newlen = Buffer.byteLength(newPath);
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + 20 + 4 + oldlen + 4 + newlen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.EXTENDED;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(20, p, true);
- buf.write('hardlink@openssh.com', p += 4, 20, 'ascii');
- buf.writeUInt32BE(oldlen, p += 20, true);
- buf.write(oldPath, p += 4, oldlen, 'utf8');
- buf.writeUInt32BE(newlen, p += oldlen, true);
- buf.write(newPath, p += 4, newlen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing hardlink@openssh.com');
- return this.push(buf);
- };
- SFTPStream.prototype.ext_openssh_fsync = function(handle, cb) {
- var state = this._state;
- if (this.server)
- throw new Error('Client-only method called in server mode');
- else if (!state.extensions['fsync@openssh.com']
- || state.extensions['fsync@openssh.com'].indexOf('1') === -1)
- throw new Error('Server does not support this extended request');
- else if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- /*
- uint32 id
- string "fsync@openssh.com"
- string handle
- */
- var handlelen = handle.length;
- var p = 9;
- var buf = new Buffer(4 + 1 + 4 + 4 + 17 + 4 + handlelen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = REQUEST.EXTENDED;
- var reqid = state.writeReqid = (state.writeReqid + 1) % MAX_REQID;
- buf.writeUInt32BE(reqid, 5, true);
- buf.writeUInt32BE(17, p, true);
- buf.write('fsync@openssh.com', p += 4, 17, 'ascii');
- buf.writeUInt32BE(handlelen, p += 17, true);
- buf.write(handle, p += 4, handlelen, 'utf8');
- state.requests[reqid] = { cb: cb };
- this.debug('DEBUG[SFTP]: Outgoing: Writing fsync@openssh.com');
- return this.push(buf);
- };
- // server
- SFTPStream.prototype.status = function(id, code, message, lang) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- if (!STATUS_CODE[code] || typeof code !== 'number')
- throw new Error('Bad status code: ' + code);
- message || (message = '');
- lang || (lang = '');
- var msgLen = Buffer.byteLength(message);
- var langLen = Buffer.byteLength(lang);
- var buf = new Buffer(4 + 1 + 4 + 4 + 4 + msgLen + 4 + langLen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = RESPONSE.STATUS;
- buf.writeUInt32BE(id, 5, true);
- buf.writeUInt32BE(code, 9, true);
- buf.writeUInt32BE(msgLen, 13, true);
- if (msgLen)
- buf.write(message, 17, msgLen, 'utf8');
- buf.writeUInt32BE(langLen, 17 + msgLen, true);
- if (langLen)
- buf.write(lang, 17 + msgLen + 4, langLen, 'ascii');
- this.debug('DEBUG[SFTP]: Outgoing: Writing STATUS');
- return this.push(buf);
- };
- SFTPStream.prototype.handle = function(id, handle) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- var handleLen = handle.length;
- if (handleLen > 256)
- throw new Error('handle too large (> 256 bytes)');
- var buf = new Buffer(4 + 1 + 4 + 4 + handleLen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = RESPONSE.HANDLE;
- buf.writeUInt32BE(id, 5, true);
- buf.writeUInt32BE(handleLen, 9, true);
- if (handleLen)
- handle.copy(buf, 13);
- this.debug('DEBUG[SFTP]: Outgoing: Writing HANDLE');
- return this.push(buf);
- };
- SFTPStream.prototype.data = function(id, data, encoding) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- var isBuffer = Buffer.isBuffer(data);
- if (!isBuffer && typeof data !== 'string')
- throw new Error('data is not a Buffer or string');
- if (!isBuffer)
- encoding || (encoding = 'utf8');
- var dataLen = (isBuffer ? data.length : Buffer.byteLength(data, encoding));
- var buf = new Buffer(4 + 1 + 4 + 4 + dataLen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = RESPONSE.DATA;
- buf.writeUInt32BE(id, 5, true);
- buf.writeUInt32BE(dataLen, 9, true);
- if (dataLen) {
- if (isBuffer)
- data.copy(buf, 13);
- else
- buf.write(data, 13, dataLen, encoding);
- }
- this.debug('DEBUG[SFTP]: Outgoing: Writing DATA');
- return this.push(buf);
- };
- SFTPStream.prototype.name = function(id, names) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- if (!Array.isArray(names) && typeof names === 'object')
- names = [ names ];
- else if (!Array.isArray(names))
- throw new Error('names is not an object or array');
- var count = names.length;
- var namesLen = 0;
- var nameAttrs;
- var attrs = [];
- var name;
- var filename;
- var longname;
- var attr;
- var len;
- var len2;
- var buf;
- var p;
- var i;
- var j;
- var k;
- for (i = 0; i < count; ++i) {
- name = names[i];
- filename = (!name || !name.filename || typeof name.filename !== 'string'
- ? ''
- : name.filename);
- namesLen += 4 + Buffer.byteLength(filename);
- longname = (!name || !name.longname || typeof name.longname !== 'string'
- ? ''
- : name.longname);
- namesLen += 4 + Buffer.byteLength(longname);
- if (typeof name.attrs === 'object') {
- nameAttrs = attrsToBytes(name.attrs);
- namesLen += 4 + nameAttrs.nbytes;
- attrs.push(nameAttrs);
- } else {
- namesLen += 4;
- attrs.push(null);
- }
- }
- buf = new Buffer(4 + 1 + 4 + 4 + namesLen);
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = RESPONSE.NAME;
- buf.writeUInt32BE(id, 5, true);
- buf.writeUInt32BE(count, 9, true);
- p = 13;
- for (i = 0; i < count; ++i) {
- name = names[i];
- filename = (!name || !name.filename || typeof name.filename !== 'string'
- ? ''
- : name.filename);
- len = Buffer.byteLength(filename);
- buf.writeUInt32BE(len, p, true);
- p += 4;
- if (len) {
- buf.write(filename, p, len, 'utf8');
- p += len;
- }
- longname = (!name || !name.longname || typeof name.longname !== 'string'
- ? ''
- : name.longname);
- len = Buffer.byteLength(longname);
- buf.writeUInt32BE(len, p, true);
- p += 4;
- if (len) {
- buf.write(longname, p, len, 'utf8');
- p += len;
- }
- attr = attrs[i];
- if (attr) {
- buf.writeUInt32BE(attr.flags, p, true);
- p += 4;
- if (attr.flags && attr.bytes) {
- var bytes = attr.bytes;
- for (j = 0, len = bytes.length; j < len; ++j)
- for (k = 0, len2 = bytes[j].length; k < len2; ++k)
- buf[p++] = bytes[j][k];
- }
- } else {
- buf.writeUInt32BE(0, p, true);
- p += 4;
- }
- }
- this.debug('DEBUG[SFTP]: Outgoing: Writing NAME');
- return this.push(buf);
- };
- SFTPStream.prototype.attrs = function(id, attrs) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- if (typeof attrs !== 'object')
- throw new Error('attrs is not an object');
- var info = attrsToBytes(attrs);
- var buf = new Buffer(4 + 1 + 4 + 4 + info.nbytes);
- var p = 13;
- buf.writeUInt32BE(buf.length - 4, 0, true);
- buf[4] = RESPONSE.ATTRS;
- buf.writeUInt32BE(id, 5, true);
- buf.writeUInt32BE(info.flags, 9, true);
- if (info.flags && info.bytes) {
- var bytes = info.bytes;
- for (var j = 0, len = bytes.length; j < len; ++j)
- for (var k = 0, len2 = bytes[j].length; k < len2; ++k)
- buf[p++] = bytes[j][k];
- }
- this.debug('DEBUG[SFTP]: Outgoing: Writing ATTRS');
- return this.push(buf);
- };
- function readAttrs(buf, p, stream, callback) {
- /*
- uint32 flags
- uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
- uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID
- uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID
- uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
- uint32 atime present only if flag SSH_FILEXFER_ACMODTIME
- uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME
- uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
- string extended_type
- string extended_data
- ... more extended data (extended_type - extended_data pairs),
- so that number of pairs equals extended_count
- */
- var flags = buf.readUInt32BE(p, true);
- var attrs = new Stats();
- p += 4;
- if (flags & ATTR.SIZE) {
- var size = readUInt64BE(buf, p, stream, callback);
- if (size === false)
- return false;
- attrs.size = size;
- p += 8;
- }
- if (flags & ATTR.UIDGID) {
- var uid;
- var gid;
- uid = readInt(buf, p, this, callback);
- if (uid === false)
- return false;
- attrs.uid = uid;
- p += 4;
- gid = readInt(buf, p, this, callback);
- if (gid === false)
- return false;
- attrs.gid = gid;
- p += 4;
- }
- if (flags & ATTR.PERMISSIONS) {
- var mode = readInt(buf, p, this, callback);
- if (mode === false)
- return false;
- attrs.mode = mode;
- // backwards compatibility
- attrs.permissions = mode;
- p += 4;
- }
- if (flags & ATTR.ACMODTIME) {
- var atime;
- var mtime;
- atime = readInt(buf, p, this, callback);
- if (atime === false)
- return false;
- attrs.atime = atime;
- p += 4;
- mtime = readInt(buf, p, this, callback);
- if (mtime === false)
- return false;
- attrs.mtime = mtime;
- p += 4;
- }
- if (flags & ATTR.EXTENDED) {
- // TODO: read/parse extended data
- var extcount = readInt(buf, p, this, callback);
- if (extcount === false)
- return false;
- p += 4;
- for (var i = 0, len; i < extcount; ++i) {
- len = readInt(buf, p, this, callback);
- if (len === false)
- return false;
- p += 4 + len;
- }
- }
- buf._pos = p;
- return attrs;
- }
- function readUInt64BE(buffer, p, stream, callback) {
- if ((buffer.length - p) < 8) {
- stream && stream._cleanup(callback);
- return false;
- }
- var val = 0;
- for (var len = p + 8; p < len; ++p) {
- val *= 256;
- val += buffer[p];
- }
- buffer._pos = p;
- return val;
- }
- function attrsToBytes(attrs) {
- var flags = 0;
- var attrBytes = 0;
- var ret = [];
- var i = 0;
- if (typeof attrs.size === 'number') {
- flags |= ATTR.SIZE;
- attrBytes += 8;
- var sizeBytes = new Array(8);
- var val = attrs.size;
- for (i = 7; i >= 0; --i) {
- sizeBytes[i] = val & 0xFF;
- val /= 256;
- }
- ret.push(sizeBytes);
- }
- if (typeof attrs.uid === 'number' && typeof attrs.gid === 'number') {
- flags |= ATTR.UIDGID;
- attrBytes += 8;
- ret.push([(attrs.uid >> 24) & 0xFF, (attrs.uid >> 16) & 0xFF,
- (attrs.uid >> 8) & 0xFF, attrs.uid & 0xFF]);
- ret.push([(attrs.gid >> 24) & 0xFF, (attrs.gid >> 16) & 0xFF,
- (attrs.gid >> 8) & 0xFF, attrs.gid & 0xFF]);
- }
- if (typeof attrs.permissions === 'number'
- || typeof attrs.permissions === 'string'
- || typeof attrs.mode === 'number'
- || typeof attrs.mode === 'string') {
- var mode = modeNum(attrs.mode || attrs.permissions);
- flags |= ATTR.PERMISSIONS;
- attrBytes += 4;
- ret.push([(mode >> 24) & 0xFF,
- (mode >> 16) & 0xFF,
- (mode >> 8) & 0xFF,
- mode & 0xFF]);
- }
- if ((typeof attrs.atime === 'number' || isDate(attrs.atime))
- && (typeof attrs.mtime === 'number' || isDate(attrs.mtime))) {
- var atime = toUnixTimestamp(attrs.atime);
- var mtime = toUnixTimestamp(attrs.mtime);
- flags |= ATTR.ACMODTIME;
- attrBytes += 8;
- ret.push([(atime >> 24) & 0xFF, (atime >> 16) & 0xFF,
- (atime >> 8) & 0xFF, atime & 0xFF]);
- ret.push([(mtime >> 24) & 0xFF, (mtime >> 16) & 0xFF,
- (mtime >> 8) & 0xFF, mtime & 0xFF]);
- }
- // TODO: extended attributes
- return { flags: flags, nbytes: attrBytes, bytes: ret };
- }
- function toUnixTimestamp(time) {
- if (typeof time === 'number' && !isNaN(time))
- return time;
- else if (isDate(time))
- return parseInt(time.getTime() / 1000, 10);
- throw new Error('Cannot parse time: ' + time);
- }
- function modeNum(mode) {
- if (typeof mode === 'number' && !isNaN(mode))
- return mode;
- else if (typeof mode === 'string')
- return modeNum(parseInt(mode, 8));
- throw new Error('Cannot parse mode: ' + mode);
- }
- var stringFlagMap = {
- 'r': OPEN_MODE.READ,
- 'r+': OPEN_MODE.READ | OPEN_MODE.WRITE,
- 'w': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE,
- 'wx': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE | OPEN_MODE.EXCL,
- 'xw': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE | OPEN_MODE.EXCL,
- 'w+': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE,
- 'wx+': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE
- | OPEN_MODE.EXCL,
- 'xw+': OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE
- | OPEN_MODE.EXCL,
- 'a': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.WRITE,
- 'ax': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.WRITE | OPEN_MODE.EXCL,
- 'xa': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.WRITE | OPEN_MODE.EXCL,
- 'a+': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE,
- 'ax+': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE
- | OPEN_MODE.EXCL,
- 'xa+': OPEN_MODE.APPEND | OPEN_MODE.CREAT | OPEN_MODE.READ | OPEN_MODE.WRITE
- | OPEN_MODE.EXCL
- };
- var stringFlagMapKeys = Object.keys(stringFlagMap);
- function stringToFlags(str) {
- var flags = stringFlagMap[str];
- if (flags !== undefined)
- return flags;
- return null;
- }
- SFTPStream.stringToFlags = stringToFlags;
- function flagsToString(flags) {
- for (var i = 0; i < stringFlagMapKeys.length; ++i) {
- var key = stringFlagMapKeys[i];
- if (stringFlagMap[key] === flags)
- return key;
- }
- return null;
- }
- SFTPStream.flagsToString = flagsToString;
- function Stats(initial) {
- this.mode = (initial && initial.mode);
- this.permissions = this.mode; // backwards compatiblity
- this.uid = (initial && initial.uid);
- this.gid = (initial && initial.gid);
- this.size = (initial && initial.size);
- this.atime = (initial && initial.atime);
- this.mtime = (initial && initial.mtime);
- }
- Stats.prototype._checkModeProperty = function(property) {
- return ((this.mode & constants.S_IFMT) === property);
- };
- Stats.prototype.isDirectory = function() {
- return this._checkModeProperty(constants.S_IFDIR);
- };
- Stats.prototype.isFile = function() {
- return this._checkModeProperty(constants.S_IFREG);
- };
- Stats.prototype.isBlockDevice = function() {
- return this._checkModeProperty(constants.S_IFBLK);
- };
- Stats.prototype.isCharacterDevice = function() {
- return this._checkModeProperty(constants.S_IFCHR);
- };
- Stats.prototype.isSymbolicLink = function() {
- return this._checkModeProperty(constants.S_IFLNK);
- };
- Stats.prototype.isFIFO = function() {
- return this._checkModeProperty(constants.S_IFIFO);
- };
- Stats.prototype.isSocket = function() {
- return this._checkModeProperty(constants.S_IFSOCK);
- };
- SFTPStream.Stats = Stats;
- // ReadStream-related
- var kMinPoolSpace = 128;
- var pool;
- function allocNewPool(poolSize) {
- pool = new Buffer(poolSize);
- pool.used = 0;
- }
- function ReadStream(sftp, path, options) {
- if (!(this instanceof ReadStream))
- return new ReadStream(sftp, path, options);
- var self = this;
- if (options === undefined)
- options = {};
- else if (typeof options === 'string')
- options = { encoding: options };
- else if (options === null || typeof options !== 'object')
- throw new TypeError('"options" argument must be a string or an object');
- else
- options = Object.create(options);
- // a little bit bigger buffer and water marks by default
- if (options.highWaterMark === undefined)
- options.highWaterMark = 64 * 1024;
- ReadableStream.call(this, options);
- this.path = path;
- this.handle = options.handle === undefined ? null : options.handle;
- this.flags = options.flags === undefined ? 'r' : options.flags;
- this.mode = options.mode === undefined ? 438/*0666*/ : options.mode;
- this.start = options.start === undefined ? undefined : options.start;
- this.end = options.end === undefined ? undefined : options.end;
- this.autoClose = options.autoClose === undefined ? true : options.autoClose;
- this.pos = 0;
- this.sftp = sftp;
- if (this.start !== undefined) {
- if (typeof this.start !== 'number')
- throw new TypeError('start must be a Number');
- if (this.end === undefined)
- this.end = Infinity;
- else if (typeof this.end !== 'number')
- throw new TypeError('end must be a Number');
- if (this.start > this.end)
- throw new Error('start must be <= end');
- else if (this.start < 0)
- throw new Error('start must be >= zero');
- this.pos = this.start;
- }
- this.on('end', function() {
- if (self.autoClose) {
- self.destroy();
- }
- });
- if (!Buffer.isBuffer(this.handle))
- this.open();
- }
- inherits(ReadStream, ReadableStream);
- ReadStream.prototype.open = function() {
- var self = this;
- this.sftp.open(this.path, this.flags, this.mode, function(er, handle) {
- if (er) {
- self.emit('error', er);
- this.destroyed = this.closed = true;
- self.emit('close');
- return;
- }
- self.handle = handle;
- self.emit('open', handle);
- // start the flow of data.
- self.read();
- });
- };
- ReadStream.prototype._read = function(n) {
- if (!Buffer.isBuffer(this.handle)) {
- return this.once('open', function() {
- this._read(n);
- });
- }
- if (this.destroyed)
- return;
- if (!pool || pool.length - pool.used < kMinPoolSpace) {
- // discard the old pool.
- pool = null;
- allocNewPool(this._readableState.highWaterMark);
- }
- // Grab another reference to the pool in the case that while we're
- // in the thread pool another read() finishes up the pool, and
- // allocates a new one.
- var thisPool = pool;
- var toRead = Math.min(pool.length - pool.used, n);
- var start = pool.used;
- if (this.end !== undefined)
- toRead = Math.min(this.end - this.pos + 1, toRead);
- // already read everything we were supposed to read!
- // treat as EOF.
- if (toRead <= 0)
- return this.push(null);
- // the actual read.
- var self = this;
- this.sftp.readData(this.handle, pool, pool.used, toRead, this.pos, onread);
- // move the pool positions, and internal position for reading.
- this.pos += toRead;
- pool.used += toRead;
- function onread(er, bytesRead) {
- if (er) {
- if (self.autoClose)
- self.destroy();
- self.emit('error', er);
- } else {
- var b = null;
- if (bytesRead > 0)
- b = thisPool.slice(start, start + bytesRead);
- self.push(b);
- }
- }
- };
- ReadStream.prototype.destroy = function() {
- if (this.destroyed)
- return;
- this.destroyed = true;
- if (Buffer.isBuffer(this.handle))
- this.close();
- };
- ReadStream.prototype.close = function(cb) {
- var self = this;
- if (cb)
- this.once('close', cb);
- if (this.closed || !Buffer.isBuffer(this.handle)) {
- if (!Buffer.isBuffer(this.handle)) {
- this.once('open', close);
- return;
- }
- return process.nextTick(this.emit.bind(this, 'close'));
- }
- this.closed = true;
- close();
- function close(handle) {
- self.sftp.close(handle || self.handle, function(er) {
- if (er)
- self.emit('error', er);
- else
- self.emit('close');
- });
- self.handle = null;
- }
- };
- function WriteStream(sftp, path, options) {
- if (!(this instanceof WriteStream))
- return new WriteStream(sftp, path, options);
- if (options === undefined)
- options = {};
- else if (typeof options === 'string')
- options = { encoding: options };
- else if (options === null || typeof options !== 'object')
- throw new TypeError('"options" argument must be a string or an object');
- else
- options = Object.create(options);
- WritableStream.call(this, options);
- this.path = path;
- this.handle = options.handle === undefined ? null : options.handle;
- this.flags = options.flags === undefined ? 'w' : options.flags;
- this.mode = options.mode === undefined ? 438/*0666*/ : options.mode;
- this.start = options.start === undefined ? undefined : options.start;
- this.autoClose = options.autoClose === undefined ? true : options.autoClose;
- this.pos = 0;
- this.bytesWritten = 0;
- this.sftp = sftp;
- if (this.start !== undefined) {
- if (typeof this.start !== 'number')
- throw new TypeError('start must be a Number');
- if (this.start < 0)
- throw new Error('start must be >= zero');
- this.pos = this.start;
- }
- if (options.encoding)
- this.setDefaultEncoding(options.encoding);
- if (!Buffer.isBuffer(this.handle))
- this.open();
- // dispose on finish.
- this.once('finish', function onclose() {
- if (this.autoClose)
- this.close();
- });
- }
- inherits(WriteStream, WritableStream);
- WriteStream.prototype.open = function() {
- var self = this;
- this.sftp.open(this.path, this.flags, this.mode, function(er, handle) {
- if (er) {
- self.emit('error', er);
- if (self.autoClose) {
- self.destroyed = self.closed = true;
- self.emit('close');
- }
- return;
- }
- self.handle = handle;
- self.sftp.fchmod(handle, self.mode, function tryAgain(err) {
- if (err) {
- // Try chmod() for sftp servers that may not support fchmod() for
- // whatever reason
- self.sftp.chmod(self.path, self.mode, function(err_) {
- tryAgain();
- });
- return;
- }
- // SFTPv3 requires absolute offsets, no matter the open flag used
- if (self.flags[0] === 'a') {
- self.sftp.fstat(handle, function tryStat(err, st) {
- if (err) {
- // Try stat() for sftp servers that may not support fstat() for
- // whatever reason
- self.sftp.stat(self.path, function(err_, st_) {
- if (err_) {
- self.destroy();
- self.emit('error', err);
- return;
- }
- tryStat(null, st_);
- });
- return;
- }
- self.pos = st.size;
- self.emit('open', handle);
- });
- return;
- }
- self.emit('open', handle);
- });
- });
- };
- WriteStream.prototype._write = function(data, encoding, cb) {
- if (!Buffer.isBuffer(data))
- return this.emit('error', new Error('Invalid data'));
- if (!Buffer.isBuffer(this.handle)) {
- return this.once('open', function() {
- this._write(data, encoding, cb);
- });
- }
- var self = this;
- this.sftp.writeData(this.handle,
- data,
- 0,
- data.length,
- this.pos,
- function(er, bytes) {
- if (er) {
- if (self.autoClose)
- self.destroy();
- return cb(er);
- }
- self.bytesWritten += bytes;
- cb();
- });
- this.pos += data.length;
- };
- WriteStream.prototype._writev = function(data, cb) {
- if (!Buffer.isBuffer(this.handle)) {
- return this.once('open', function() {
- this._writev(data, cb);
- });
- }
- var sftp = this.sftp;
- var handle = this.handle;
- var writesLeft = data.length;
- var self = this;
- for (var i = 0; i < data.length; ++i) {
- var chunk = data[i].chunk;
- sftp.writeData(handle, chunk, 0, chunk.length, this.pos, onwrite);
- this.pos += chunk.length;
- }
- function onwrite(er, bytes) {
- if (er) {
- self.destroy();
- return cb(er);
- }
- self.bytesWritten += bytes;
- if (--writesLeft === 0)
- cb();
- }
- };
- WriteStream.prototype.destroy = ReadStream.prototype.destroy;
- WriteStream.prototype.close = ReadStream.prototype.close;
- // There is no shutdown() for files.
- WriteStream.prototype.destroySoon = WriteStream.prototype.end;
- module.exports = SFTPStream;
|