1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778 |
- 'use strict';
- const EventEmitter = require('events');
- const fs = require('fs');
- const { constants } = fs;
- const {
- Readable: ReadableStream,
- Writable: WritableStream
- } = require('stream');
- const { inherits, isDate } = require('util');
- const FastBuffer = Buffer[Symbol.species];
- const {
- bufferCopy,
- bufferSlice,
- makeBufferParser,
- writeUInt32BE,
- } = require('./utils.js');
- const ATTR = {
- SIZE: 0x00000001,
- UIDGID: 0x00000002,
- PERMISSIONS: 0x00000004,
- ACMODTIME: 0x00000008,
- EXTENDED: 0x80000000,
- };
- // Large enough to store all possible attributes
- const ATTRS_BUF = Buffer.alloc(28);
- const 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
- };
- const VALID_STATUS_CODES = new Map(
- Object.values(STATUS_CODE).map((n) => [n, 1])
- );
- const STATUS_CODE_STR = {
- [STATUS_CODE.OK]: 'No error',
- [STATUS_CODE.EOF]: 'End of file',
- [STATUS_CODE.NO_SUCH_FILE]: 'No such file or directory',
- [STATUS_CODE.PERMISSION_DENIED]: 'Permission denied',
- [STATUS_CODE.FAILURE]: 'Failure',
- [STATUS_CODE.BAD_MESSAGE]: 'Bad message',
- [STATUS_CODE.NO_CONNECTION]: 'No connection',
- [STATUS_CODE.CONNECTION_LOST]: 'Connection lost',
- [STATUS_CODE.OP_UNSUPPORTED]: 'Operation unsupported',
- };
- const 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
- };
- const RESPONSE = {
- VERSION: 2,
- STATUS: 101,
- HANDLE: 102,
- DATA: 103,
- NAME: 104,
- ATTRS: 105,
- EXTENDED: 201
- };
- const OPEN_MODE = {
- READ: 0x00000001,
- WRITE: 0x00000002,
- APPEND: 0x00000004,
- CREAT: 0x00000008,
- TRUNC: 0x00000010,
- EXCL: 0x00000020
- };
- const PKT_RW_OVERHEAD = 2 * 1024;
- const MAX_REQID = 2 ** 32 - 1;
- const CLIENT_VERSION_BUFFER = Buffer.from([
- 0, 0, 0, 5 /* length */,
- REQUEST.INIT,
- 0, 0, 0, 3 /* version */
- ]);
- const SERVER_VERSION_BUFFER = Buffer.from([
- 0, 0, 0, 5 /* length */,
- RESPONSE.VERSION,
- 0, 0, 0, 3 /* version */
- ]);
- const RE_OPENSSH = /^SSH-2.0-(?:OpenSSH|dropbear)/;
- const OPENSSH_MAX_PKT_LEN = 256 * 1024;
- const bufferParser = makeBufferParser();
- const fakeStderr = {
- readable: false,
- writable: false,
- push: (data) => {},
- once: () => {},
- on: () => {},
- emit: () => {},
- end: () => {},
- };
- function noop() {}
- // Emulates enough of `Channel` to be able to be used as a drop-in replacement
- // in order to process incoming data with as little overhead as possible
- class SFTP extends EventEmitter {
- constructor(client, chanInfo, cfg) {
- super();
- if (typeof cfg !== 'object' || !cfg)
- cfg = {};
- const remoteIdentRaw = client._protocol._remoteIdentRaw;
- this.server = !!cfg.server;
- this._debug = (typeof cfg.debug === 'function' ? cfg.debug : undefined);
- this._isOpenSSH = (remoteIdentRaw && RE_OPENSSH.test(remoteIdentRaw));
- this._version = -1;
- this._extensions = {};
- this._biOpt = cfg.biOpt;
- this._pktLenBytes = 0;
- this._pktLen = 0;
- this._pktPos = 0;
- this._pktType = 0;
- this._pktData = undefined;
- this._writeReqid = -1;
- this._requests = {};
- this._maxInPktLen = OPENSSH_MAX_PKT_LEN;
- this._maxOutPktLen = 34000;
- this._maxReadLen =
- (this._isOpenSSH ? OPENSSH_MAX_PKT_LEN : 34000) - PKT_RW_OVERHEAD;
- this._maxWriteLen =
- (this._isOpenSSH ? OPENSSH_MAX_PKT_LEN : 34000) - PKT_RW_OVERHEAD;
- this.maxOpenHandles = undefined;
- // Channel compatibility
- this._client = client;
- this._protocol = client._protocol;
- this._callbacks = [];
- this._hasX11 = false;
- this._exit = {
- code: undefined,
- signal: undefined,
- dump: undefined,
- desc: undefined,
- };
- this._waitWindow = false; // SSH-level backpressure
- this._chunkcb = undefined;
- this._buffer = [];
- this.type = chanInfo.type;
- this.subtype = undefined;
- this.incoming = chanInfo.incoming;
- this.outgoing = chanInfo.outgoing;
- this.stderr = fakeStderr;
- this.readable = true;
- }
- // This handles incoming data to parse
- push(data) {
- if (data === null) {
- cleanupRequests(this);
- if (!this.readable)
- return;
- // No more incoming data from the remote side
- this.readable = false;
- this.emit('end');
- return;
- }
- /*
- uint32 length
- byte type
- byte[length - 1] data payload
- */
- let p = 0;
- while (p < data.length) {
- if (this._pktLenBytes < 4) {
- let nb = Math.min(4 - this._pktLenBytes, data.length - p);
- this._pktLenBytes += nb;
- while (nb--)
- this._pktLen = (this._pktLen << 8) + data[p++];
- if (this._pktLenBytes < 4)
- return;
- if (this._pktLen === 0)
- return doFatalSFTPError(this, 'Invalid packet length');
- if (this._pktLen > this._maxInPktLen) {
- const max = this._maxInPktLen;
- return doFatalSFTPError(
- this,
- `Packet length ${this._pktLen} exceeds max length of ${max}`
- );
- }
- if (p >= data.length)
- return;
- }
- if (this._pktPos < this._pktLen) {
- const nb = Math.min(this._pktLen - this._pktPos, data.length - p);
- if (p !== 0 || nb !== data.length) {
- if (nb === this._pktLen) {
- this._pkt = new FastBuffer(data.buffer, data.byteOffset + p, nb);
- } else {
- if (!this._pkt)
- this._pkt = Buffer.allocUnsafe(this._pktLen);
- this._pkt.set(
- new Uint8Array(data.buffer, data.byteOffset + p, nb),
- this._pktPos
- );
- }
- } else if (nb === this._pktLen) {
- this._pkt = data;
- } else {
- if (!this._pkt)
- this._pkt = Buffer.allocUnsafe(this._pktLen);
- this._pkt.set(data, this._pktPos);
- }
- p += nb;
- this._pktPos += nb;
- if (this._pktPos < this._pktLen)
- return;
- }
- const type = this._pkt[0];
- const payload = this._pkt;
- // Prepare for next packet
- this._pktLen = 0;
- this._pktLenBytes = 0;
- this._pkt = undefined;
- this._pktPos = 0;
- const handler = (this.server
- ? SERVER_HANDLERS[type]
- : CLIENT_HANDLERS[type]);
- if (!handler)
- return doFatalSFTPError(this, `Unknown packet type ${type}`);
- if (this._version === -1) {
- if (this.server) {
- if (type !== REQUEST.INIT)
- return doFatalSFTPError(this, `Expected INIT packet, got ${type}`);
- } else if (type !== RESPONSE.VERSION) {
- return doFatalSFTPError(this, `Expected VERSION packet, got ${type}`);
- }
- }
- if (handler(this, payload) === false)
- return;
- }
- }
- end() {
- this.destroy();
- }
- destroy() {
- if (this.outgoing.state === 'open' || this.outgoing.state === 'eof') {
- this.outgoing.state = 'closing';
- this._protocol.channelClose(this.outgoing.id);
- }
- }
- _init() {
- this._init = noop;
- if (!this.server)
- sendOrBuffer(this, CLIENT_VERSION_BUFFER);
- }
- // ===========================================================================
- // Client-specific ===========================================================
- // ===========================================================================
- createReadStream(path, options) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- return new ReadStream(this, path, options);
- }
- createWriteStream(path, options) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- return new WriteStream(this, path, options);
- }
- open(path, flags_, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- if (typeof attrs === 'function') {
- cb = attrs;
- attrs = undefined;
- }
- const flags = (typeof flags_ === 'number' ? flags_ : stringToFlags(flags_));
- if (flags === null)
- throw new Error(`Unknown flags string: ${flags_}`);
- let attrsFlags = 0;
- let attrsLen = 0;
- if (typeof attrs === 'string' || typeof attrs === 'number')
- attrs = { mode: attrs };
- if (typeof attrs === 'object' && attrs !== null) {
- attrs = attrsToBytes(attrs);
- attrsFlags = attrs.flags;
- attrsLen = attrs.nb;
- }
- /*
- uint32 id
- string filename
- uint32 pflags
- ATTRS attrs
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + 4 + attrsLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.OPEN;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- writeUInt32BE(buf, flags, p += pathLen);
- writeUInt32BE(buf, attrsFlags, p += 4);
- if (attrsLen) {
- p += 4;
- if (attrsLen === ATTRS_BUF.length)
- buf.set(ATTRS_BUF, p);
- else
- bufferCopy(ATTRS_BUF, buf, 0, attrsLen, p);
- p += attrsLen;
- }
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} OPEN`
- );
- }
- close(handle, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- /*
- uint32 id
- string handle
- */
- const handleLen = handle.length;
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + handleLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.CLOSE;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, handleLen, p);
- buf.set(handle, p += 4);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} CLOSE`
- );
- }
- read(handle, buf, off, len, position, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- if (!Buffer.isBuffer(buf))
- throw new Error('buffer is not a Buffer');
- if (off >= buf.length)
- throw new Error('offset is out of bounds');
- if (off + len > buf.length)
- throw new Error('length extends beyond buffer');
- if (position === null)
- throw new Error('null position currently unsupported');
- read_(this, handle, buf, off, len, position, cb);
- }
- readData(handle, buf, off, len, position, cb) {
- // Backwards compatibility
- this.read(handle, buf, off, len, position, cb);
- }
- write(handle, buf, off, len, position, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- if (!Buffer.isBuffer(buf))
- throw new Error('buffer is not a Buffer');
- if (off > buf.length)
- throw new Error('offset is out of bounds');
- if (off + len > buf.length)
- throw new Error('length extends beyond buffer');
- if (position === null)
- throw new Error('null position currently unsupported');
- if (!len) {
- cb && process.nextTick(cb, undefined, 0);
- return;
- }
- const maxDataLen = this._maxWriteLen;
- const overflow = Math.max(len - maxDataLen, 0);
- const origPosition = position;
- if (overflow)
- len = maxDataLen;
- /*
- uint32 id
- string handle
- uint64 offset
- string data
- */
- const handleLen = handle.length;
- let p = 9;
- const out = Buffer.allocUnsafe(4 + 1 + 4 + 4 + handleLen + 8 + 4 + len);
- writeUInt32BE(out, out.length - 4, 0);
- out[4] = REQUEST.WRITE;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(out, reqid, 5);
- writeUInt32BE(out, handleLen, p);
- out.set(handle, p += 4);
- p += handleLen;
- for (let i = 7; i >= 0; --i) {
- out[p + i] = position & 0xFF;
- position /= 256;
- }
- writeUInt32BE(out, len, p += 8);
- bufferCopy(buf, out, off, off + len, p += 4);
- this._requests[reqid] = {
- cb: (err) => {
- if (err) {
- if (typeof cb === 'function')
- cb(err);
- } else if (overflow) {
- this.write(handle,
- buf,
- off + len,
- overflow,
- origPosition + len,
- cb);
- } else if (typeof cb === 'function') {
- cb(undefined, off + len);
- }
- }
- };
- const isSent = sendOrBuffer(this, out);
- if (this._debug) {
- const how = (isSent ? 'Sent' : 'Buffered');
- this._debug(`SFTP: Outbound: ${how} WRITE (id:${reqid})`);
- }
- }
- writeData(handle, buf, off, len, position, cb) {
- // Backwards compatibility
- this.write(handle, buf, off, len, position, cb);
- }
- fastGet(remotePath, localPath, opts, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- fastXfer(this, fs, remotePath, localPath, opts, cb);
- }
- fastPut(localPath, remotePath, opts, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- fastXfer(fs, this, localPath, remotePath, opts, cb);
- }
- readFile(path, options, callback_) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- let callback;
- if (typeof callback_ === 'function') {
- callback = callback_;
- } else if (typeof options === 'function') {
- callback = options;
- options = undefined;
- }
- 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');
- const encoding = options.encoding;
- if (encoding && !Buffer.isEncoding(encoding))
- throw new Error(`Unknown encoding: ${encoding}`);
- // First stat the file, so we know the size.
- let size;
- let buffer; // Single buffer with file data
- let buffers; // List for when size is unknown
- let pos = 0;
- let handle;
- // SFTPv3 does not support using -1 for read position, so we have to track
- // read position manually
- let bytesRead = 0;
- const flag = options.flag || 'r';
- const read = () => {
- if (size === 0) {
- buffer = Buffer.allocUnsafe(8192);
- this.read(handle, buffer, 0, 8192, bytesRead, afterRead);
- } else {
- this.read(handle, buffer, pos, size - pos, bytesRead, afterRead);
- }
- };
- const afterRead = (er, nbytes) => {
- let eof;
- if (er) {
- eof = (er.code === STATUS_CODE.EOF);
- if (!eof) {
- return this.close(handle, () => {
- return callback && callback(er);
- });
- }
- } else {
- eof = false;
- }
- if (eof || (size === 0 && 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(bufferSlice(buffer, 0, nbytes));
- read();
- }
- };
- afterRead._wantEOFError = true;
- const close = () => {
- this.close(handle, (er) => {
- if (size === 0) {
- // Collect the data into the buffers list.
- buffer = Buffer.concat(buffers, pos);
- } else if (pos < size) {
- buffer = bufferSlice(buffer, 0, pos);
- }
- if (encoding)
- buffer = buffer.toString(encoding);
- return callback && callback(er, buffer);
- });
- };
- this.open(path, flag, 0o666, (er, handle_) => {
- if (er)
- return callback && callback(er);
- handle = handle_;
- const tryStat = (er, st) => {
- if (er) {
- // Try stat() for sftp servers that may not support fstat() for
- // whatever reason
- this.stat(path, (er_, st_) => {
- if (er_) {
- return this.close(handle, () => {
- 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 = Buffer.allocUnsafe(size);
- read();
- };
- this.fstat(handle, tryStat);
- });
- }
- writeFile(path, data, options, callback_) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- let callback;
- if (typeof callback_ === 'function') {
- callback = callback_;
- } else if (typeof options === 'function') {
- callback = options;
- options = undefined;
- }
- if (typeof options === 'string')
- options = { encoding: options, mode: 0o666, flag: 'w' };
- else if (!options)
- options = { encoding: 'utf8', mode: 0o666, 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}`);
- const flag = options.flag || 'w';
- this.open(path, flag, options.mode, (openErr, handle) => {
- if (openErr) {
- callback && callback(openErr);
- } else {
- const buffer = (Buffer.isBuffer(data)
- ? data
- : Buffer.from('' + data, options.encoding || 'utf8'));
- const 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) {
- const tryStat = (er, st) => {
- if (er) {
- // Try stat() for sftp servers that may not support fstat() for
- // whatever reason
- this.stat(path, (er_, st_) => {
- if (er_) {
- return this.close(handle, () => {
- callback && callback(er);
- });
- }
- tryStat(null, st_);
- });
- return;
- }
- writeAll(this, handle, buffer, 0, buffer.length, st.size, callback);
- };
- this.fstat(handle, tryStat);
- return;
- }
- writeAll(this, handle, buffer, 0, buffer.length, position, callback);
- }
- });
- }
- appendFile(path, data, options, callback_) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- let callback;
- if (typeof callback_ === 'function') {
- callback = callback_;
- } else if (typeof options === 'function') {
- callback = options;
- options = undefined;
- }
- if (typeof options === 'string')
- options = { encoding: options, mode: 0o666, flag: 'a' };
- else if (!options)
- options = { encoding: 'utf8', mode: 0o666, flag: 'a' };
- else if (typeof options !== 'object')
- throw new TypeError('Bad arguments');
- if (!options.flag)
- options = Object.assign({ flag: 'a' }, options);
- this.writeFile(path, data, options, callback);
- }
- exists(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- this.stat(path, (err) => {
- cb && cb(err ? false : true);
- });
- }
- unlink(filename, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string filename
- */
- const fnameLen = Buffer.byteLength(filename);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + fnameLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.REMOVE;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, fnameLen, p);
- buf.utf8Write(filename, p += 4, fnameLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} REMOVE`
- );
- }
- rename(oldPath, newPath, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string oldpath
- string newpath
- */
- const oldLen = Buffer.byteLength(oldPath);
- const newLen = Buffer.byteLength(newPath);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + oldLen + 4 + newLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.RENAME;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, oldLen, p);
- buf.utf8Write(oldPath, p += 4, oldLen);
- writeUInt32BE(buf, newLen, p += oldLen);
- buf.utf8Write(newPath, p += 4, newLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} RENAME`
- );
- }
- mkdir(path, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- let flags = 0;
- let attrsLen = 0;
- if (typeof attrs === 'function') {
- cb = attrs;
- attrs = undefined;
- }
- if (typeof attrs === 'object' && attrs !== null) {
- attrs = attrsToBytes(attrs);
- flags = attrs.flags;
- attrsLen = attrs.nb;
- }
- /*
- uint32 id
- string path
- ATTRS attrs
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + attrsLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.MKDIR;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- writeUInt32BE(buf, flags, p += pathLen);
- if (attrsLen) {
- p += 4;
- if (attrsLen === ATTRS_BUF.length)
- buf.set(ATTRS_BUF, p);
- else
- bufferCopy(ATTRS_BUF, buf, 0, attrsLen, p);
- p += attrsLen;
- }
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} MKDIR`
- );
- }
- rmdir(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string path
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.RMDIR;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} RMDIR`
- );
- }
- readdir(where, opts, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- if (typeof opts === 'function') {
- cb = opts;
- opts = {};
- }
- if (typeof opts !== 'object' || opts === null)
- opts = {};
- const 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') {
- const entries = [];
- let e = 0;
- const reread = (err, handle) => {
- if (err)
- return cb(err);
- this.readdir(handle, opts, (err, list) => {
- const eof = (err && err.code === STATUS_CODE.EOF);
- if (err && !eof)
- return this.close(handle, () => cb(err));
- if (eof) {
- return this.close(handle, (err) => {
- if (err)
- return cb(err);
- cb(undefined, entries);
- });
- }
- for (let i = 0; i < list.length; ++i, ++e)
- entries[e] = list[i];
- reread(undefined, handle);
- });
- };
- return this.opendir(where, reread);
- }
- /*
- uint32 id
- string handle
- */
- const handleLen = where.length;
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + handleLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.READDIR;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, handleLen, p);
- buf.set(where, p += 4);
- this._requests[reqid] = {
- cb: (doFilter
- ? (err, list) => {
- if (typeof cb !== 'function')
- return;
- if (err)
- return cb(err);
- for (let i = list.length - 1; i >= 0; --i) {
- if (list[i].filename === '.' || list[i].filename === '..')
- list.splice(i, 1);
- }
- cb(undefined, list);
- }
- : cb)
- };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} READDIR`
- );
- }
- fstat(handle, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- /*
- uint32 id
- string handle
- */
- const handleLen = handle.length;
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + handleLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.FSTAT;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, handleLen, p);
- buf.set(handle, p += 4);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} FSTAT`
- );
- }
- stat(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string path
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.STAT;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} STAT`
- );
- }
- lstat(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string path
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.LSTAT;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} LSTAT`
- );
- }
- opendir(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string path
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.OPENDIR;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} OPENDIR`
- );
- }
- setstat(path, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- let flags = 0;
- let attrsLen = 0;
- if (typeof attrs === 'object' && attrs !== null) {
- attrs = attrsToBytes(attrs);
- flags = attrs.flags;
- attrsLen = attrs.nb;
- } else if (typeof attrs === 'function') {
- cb = attrs;
- }
- /*
- uint32 id
- string path
- ATTRS attrs
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen + 4 + attrsLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.SETSTAT;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- writeUInt32BE(buf, flags, p += pathLen);
- if (attrsLen) {
- p += 4;
- if (attrsLen === ATTRS_BUF.length)
- buf.set(ATTRS_BUF, p);
- else
- bufferCopy(ATTRS_BUF, buf, 0, attrsLen, p);
- p += attrsLen;
- }
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} SETSTAT`
- );
- }
- fsetstat(handle, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- let flags = 0;
- let attrsLen = 0;
- if (typeof attrs === 'object' && attrs !== null) {
- attrs = attrsToBytes(attrs);
- flags = attrs.flags;
- attrsLen = attrs.nb;
- } else if (typeof attrs === 'function') {
- cb = attrs;
- }
- /*
- uint32 id
- string handle
- ATTRS attrs
- */
- const handleLen = handle.length;
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + handleLen + 4 + attrsLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.FSETSTAT;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, handleLen, p);
- buf.set(handle, p += 4);
- writeUInt32BE(buf, flags, p += handleLen);
- if (attrsLen) {
- p += 4;
- if (attrsLen === ATTRS_BUF.length)
- buf.set(ATTRS_BUF, p);
- else
- bufferCopy(ATTRS_BUF, buf, 0, attrsLen, p);
- p += attrsLen;
- }
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} FSETSTAT`
- );
- }
- futimes(handle, atime, mtime, cb) {
- return this.fsetstat(handle, {
- atime: toUnixTimestamp(atime),
- mtime: toUnixTimestamp(mtime)
- }, cb);
- }
- utimes(path, atime, mtime, cb) {
- return this.setstat(path, {
- atime: toUnixTimestamp(atime),
- mtime: toUnixTimestamp(mtime)
- }, cb);
- }
- fchown(handle, uid, gid, cb) {
- return this.fsetstat(handle, {
- uid: uid,
- gid: gid
- }, cb);
- }
- chown(path, uid, gid, cb) {
- return this.setstat(path, {
- uid: uid,
- gid: gid
- }, cb);
- }
- fchmod(handle, mode, cb) {
- return this.fsetstat(handle, {
- mode: mode
- }, cb);
- }
- chmod(path, mode, cb) {
- return this.setstat(path, {
- mode: mode
- }, cb);
- }
- readlink(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string path
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.READLINK;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- this._requests[reqid] = {
- cb: (err, names) => {
- if (typeof cb !== 'function')
- return;
- if (err)
- return cb(err);
- if (!names || !names.length)
- return cb(new Error('Response missing link info'));
- cb(undefined, names[0].filename);
- }
- };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} READLINK`
- );
- }
- symlink(targetPath, linkPath, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string linkpath
- string targetpath
- */
- const linkLen = Buffer.byteLength(linkPath);
- const targetLen = Buffer.byteLength(targetPath);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + linkLen + 4 + targetLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.SYMLINK;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- if (this._isOpenSSH) {
- // OpenSSH has linkpath and targetpath positions switched
- writeUInt32BE(buf, targetLen, p);
- buf.utf8Write(targetPath, p += 4, targetLen);
- writeUInt32BE(buf, linkLen, p += targetLen);
- buf.utf8Write(linkPath, p += 4, linkLen);
- } else {
- writeUInt32BE(buf, linkLen, p);
- buf.utf8Write(linkPath, p += 4, linkLen);
- writeUInt32BE(buf, targetLen, p += linkLen);
- buf.utf8Write(targetPath, p += 4, targetLen);
- }
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} SYMLINK`
- );
- }
- realpath(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- /*
- uint32 id
- string path
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + pathLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.REALPATH;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, pathLen, p);
- buf.utf8Write(path, p += 4, pathLen);
- this._requests[reqid] = {
- cb: (err, names) => {
- if (typeof cb !== 'function')
- return;
- if (err)
- return cb(err);
- if (!names || !names.length)
- return cb(new Error('Response missing path info'));
- cb(undefined, names[0].filename);
- }
- };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} REALPATH`
- );
- }
- // extended requests
- ext_openssh_rename(oldPath, newPath, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- const ext = this._extensions['posix-rename@openssh.com'];
- if (!ext || ext !== '1')
- throw new Error('Server does not support this extended request');
- /*
- uint32 id
- string "posix-rename@openssh.com"
- string oldpath
- string newpath
- */
- const oldLen = Buffer.byteLength(oldPath);
- const newLen = Buffer.byteLength(newPath);
- let p = 9;
- const buf =
- Buffer.allocUnsafe(4 + 1 + 4 + 4 + 24 + 4 + oldLen + 4 + newLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.EXTENDED;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, 24, p);
- buf.utf8Write('posix-rename@openssh.com', p += 4, 24);
- writeUInt32BE(buf, oldLen, p += 24);
- buf.utf8Write(oldPath, p += 4, oldLen);
- writeUInt32BE(buf, newLen, p += oldLen);
- buf.utf8Write(newPath, p += 4, newLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- if (this._debug) {
- const which = (isBuffered ? 'Buffered' : 'Sending');
- this._debug(`SFTP: Outbound: ${which} posix-rename@openssh.com`);
- }
- }
- ext_openssh_statvfs(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- const ext = this._extensions['statvfs@openssh.com'];
- if (!ext || ext !== '2')
- throw new Error('Server does not support this extended request');
- /*
- uint32 id
- string "statvfs@openssh.com"
- string path
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 19 + 4 + pathLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.EXTENDED;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, 19, p);
- buf.utf8Write('statvfs@openssh.com', p += 4, 19);
- writeUInt32BE(buf, pathLen, p += 19);
- buf.utf8Write(path, p += 4, pathLen);
- this._requests[reqid] = { extended: 'statvfs@openssh.com', cb };
- const isBuffered = sendOrBuffer(this, buf);
- if (this._debug) {
- const which = (isBuffered ? 'Buffered' : 'Sending');
- this._debug(`SFTP: Outbound: ${which} statvfs@openssh.com`);
- }
- }
- ext_openssh_fstatvfs(handle, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- const ext = this._extensions['fstatvfs@openssh.com'];
- if (!ext || ext !== '2')
- throw new Error('Server does not support this extended request');
- if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- /*
- uint32 id
- string "fstatvfs@openssh.com"
- string handle
- */
- const handleLen = handle.length;
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 20 + 4 + handleLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.EXTENDED;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, 20, p);
- buf.utf8Write('fstatvfs@openssh.com', p += 4, 20);
- writeUInt32BE(buf, handleLen, p += 20);
- buf.set(handle, p += 4);
- this._requests[reqid] = { extended: 'fstatvfs@openssh.com', cb };
- const isBuffered = sendOrBuffer(this, buf);
- if (this._debug) {
- const which = (isBuffered ? 'Buffered' : 'Sending');
- this._debug(`SFTP: Outbound: ${which} fstatvfs@openssh.com`);
- }
- }
- ext_openssh_hardlink(oldPath, newPath, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- const ext = this._extensions['hardlink@openssh.com'];
- if (ext !== '1')
- throw new Error('Server does not support this extended request');
- /*
- uint32 id
- string "hardlink@openssh.com"
- string oldpath
- string newpath
- */
- const oldLen = Buffer.byteLength(oldPath);
- const newLen = Buffer.byteLength(newPath);
- let p = 9;
- const buf =
- Buffer.allocUnsafe(4 + 1 + 4 + 4 + 20 + 4 + oldLen + 4 + newLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.EXTENDED;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, 20, p);
- buf.utf8Write('hardlink@openssh.com', p += 4, 20);
- writeUInt32BE(buf, oldLen, p += 20);
- buf.utf8Write(oldPath, p += 4, oldLen);
- writeUInt32BE(buf, newLen, p += oldLen);
- buf.utf8Write(newPath, p += 4, newLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- if (this._debug) {
- const which = (isBuffered ? 'Buffered' : 'Sending');
- this._debug(`SFTP: Outbound: ${which} hardlink@openssh.com`);
- }
- }
- ext_openssh_fsync(handle, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- const ext = this._extensions['fsync@openssh.com'];
- if (ext !== '1')
- throw new Error('Server does not support this extended request');
- if (!Buffer.isBuffer(handle))
- throw new Error('handle is not a Buffer');
- /*
- uint32 id
- string "fsync@openssh.com"
- string handle
- */
- const handleLen = handle.length;
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 17 + 4 + handleLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.EXTENDED;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, 17, p);
- buf.utf8Write('fsync@openssh.com', p += 4, 17);
- writeUInt32BE(buf, handleLen, p += 17);
- buf.set(handle, p += 4);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} fsync@openssh.com`
- );
- }
- ext_openssh_lsetstat(path, attrs, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- const ext = this._extensions['lsetstat@openssh.com'];
- if (ext !== '1')
- throw new Error('Server does not support this extended request');
- let flags = 0;
- let attrsLen = 0;
- if (typeof attrs === 'object' && attrs !== null) {
- attrs = attrsToBytes(attrs);
- flags = attrs.flags;
- attrsLen = attrs.nb;
- } else if (typeof attrs === 'function') {
- cb = attrs;
- }
- /*
- uint32 id
- string "lsetstat@openssh.com"
- string path
- ATTRS attrs
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf =
- Buffer.allocUnsafe(4 + 1 + 4 + 4 + 20 + 4 + pathLen + 4 + attrsLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.EXTENDED;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, 20, p);
- buf.utf8Write('lsetstat@openssh.com', p += 4, 20);
- writeUInt32BE(buf, pathLen, p += 20);
- buf.utf8Write(path, p += 4, pathLen);
- writeUInt32BE(buf, flags, p += pathLen);
- if (attrsLen) {
- p += 4;
- if (attrsLen === ATTRS_BUF.length)
- buf.set(ATTRS_BUF, p);
- else
- bufferCopy(ATTRS_BUF, buf, 0, attrsLen, p);
- p += attrsLen;
- }
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- if (this._debug) {
- const status = (isBuffered ? 'Buffered' : 'Sending');
- this._debug(`SFTP: Outbound: ${status} lsetstat@openssh.com`);
- }
- }
- ext_openssh_expandPath(path, cb) {
- if (this.server)
- throw new Error('Client-only method called in server mode');
- const ext = this._extensions['expand-path@openssh.com'];
- if (ext !== '1')
- throw new Error('Server does not support this extended request');
- /*
- uint32 id
- string "expand-path@openssh.com"
- string path
- */
- const pathLen = Buffer.byteLength(path);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 23 + 4 + pathLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.EXTENDED;
- const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, 23, p);
- buf.utf8Write('expand-path@openssh.com', p += 4, 23);
- writeUInt32BE(buf, pathLen, p += 20);
- buf.utf8Write(path, p += 4, pathLen);
- this._requests[reqid] = { cb };
- const isBuffered = sendOrBuffer(this, buf);
- if (this._debug) {
- const status = (isBuffered ? 'Buffered' : 'Sending');
- this._debug(`SFTP: Outbound: ${status} expand-path@openssh.com`);
- }
- }
- // ===========================================================================
- // Server-specific ===========================================================
- // ===========================================================================
- handle(reqid, 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');
- const handleLen = handle.length;
- if (handleLen > 256)
- throw new Error('handle too large (> 256 bytes)');
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + handleLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = RESPONSE.HANDLE;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, handleLen, p);
- if (handleLen)
- buf.set(handle, p += 4);
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} HANDLE`
- );
- }
- status(reqid, code, message) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- if (!VALID_STATUS_CODES.has(code))
- throw new Error(`Bad status code: ${code}`);
- message || (message = '');
- const msgLen = Buffer.byteLength(message);
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 4 + msgLen + 4);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = RESPONSE.STATUS;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, code, p);
- writeUInt32BE(buf, msgLen, p += 4);
- p += 4;
- if (msgLen) {
- buf.utf8Write(message, p, msgLen);
- p += msgLen;
- }
- writeUInt32BE(buf, 0, p); // Empty language tag
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} STATUS`
- );
- }
- data(reqid, data, encoding) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- const isBuffer = Buffer.isBuffer(data);
- if (!isBuffer && typeof data !== 'string')
- throw new Error('data is not a Buffer or string');
- let isUTF8;
- if (!isBuffer && !encoding) {
- encoding = undefined;
- isUTF8 = true;
- }
- const dataLen = (
- isBuffer
- ? data.length
- : Buffer.byteLength(data, encoding)
- );
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + dataLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = RESPONSE.DATA;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, dataLen, p);
- if (dataLen) {
- if (isBuffer)
- buf.set(data, p += 4);
- else if (isUTF8)
- buf.utf8Write(data, p += 4, dataLen);
- else
- buf.write(data, p += 4, dataLen, encoding);
- }
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} DATA`
- );
- }
- name(reqid, names) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- if (!Array.isArray(names)) {
- if (typeof names !== 'object' || names === null)
- throw new Error('names is not an object or array');
- names = [ names ];
- }
- const count = names.length;
- let namesLen = 0;
- let nameAttrs;
- const attrs = [];
- for (let i = 0; i < count; ++i) {
- const name = names[i];
- const filename = (
- !name || !name.filename || typeof name.filename !== 'string'
- ? ''
- : name.filename
- );
- namesLen += 4 + Buffer.byteLength(filename);
- const longname = (
- !name || !name.longname || typeof name.longname !== 'string'
- ? ''
- : name.longname
- );
- namesLen += 4 + Buffer.byteLength(longname);
- if (typeof name.attrs === 'object' && name.attrs !== null) {
- nameAttrs = attrsToBytes(name.attrs);
- namesLen += 4 + nameAttrs.nb;
- if (nameAttrs.nb) {
- let bytes;
- if (nameAttrs.nb === ATTRS_BUF.length) {
- bytes = new Uint8Array(ATTRS_BUF);
- } else {
- bytes = new Uint8Array(nameAttrs.nb);
- bufferCopy(ATTRS_BUF, bytes, 0, nameAttrs.nb, 0);
- }
- nameAttrs.bytes = bytes;
- }
- attrs.push(nameAttrs);
- } else {
- namesLen += 4;
- attrs.push(null);
- }
- }
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + namesLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = RESPONSE.NAME;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, count, p);
- p += 4;
- for (let i = 0; i < count; ++i) {
- const name = names[i];
- {
- const filename = (
- !name || !name.filename || typeof name.filename !== 'string'
- ? ''
- : name.filename
- );
- const len = Buffer.byteLength(filename);
- writeUInt32BE(buf, len, p);
- p += 4;
- if (len) {
- buf.utf8Write(filename, p, len);
- p += len;
- }
- }
- {
- const longname = (
- !name || !name.longname || typeof name.longname !== 'string'
- ? ''
- : name.longname
- );
- const len = Buffer.byteLength(longname);
- writeUInt32BE(buf, len, p);
- p += 4;
- if (len) {
- buf.utf8Write(longname, p, len);
- p += len;
- }
- }
- const attr = attrs[i];
- if (attr) {
- writeUInt32BE(buf, attr.flags, p);
- p += 4;
- if (attr.flags && attr.bytes) {
- buf.set(attr.bytes, p);
- p += attr.nb;
- }
- } else {
- writeUInt32BE(buf, 0, p);
- p += 4;
- }
- }
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} NAME`
- );
- }
- attrs(reqid, attrs) {
- if (!this.server)
- throw new Error('Server-only method called in client mode');
- if (typeof attrs !== 'object' || attrs === null)
- throw new Error('attrs is not an object');
- attrs = attrsToBytes(attrs);
- const flags = attrs.flags;
- const attrsLen = attrs.nb;
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + attrsLen);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = RESPONSE.ATTRS;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, flags, p);
- if (attrsLen) {
- p += 4;
- if (attrsLen === ATTRS_BUF.length)
- buf.set(ATTRS_BUF, p);
- else
- bufferCopy(ATTRS_BUF, buf, 0, attrsLen, p);
- p += attrsLen;
- }
- const isBuffered = sendOrBuffer(this, buf);
- this._debug && this._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} ATTRS`
- );
- }
- }
- function tryCreateBuffer(size) {
- try {
- return Buffer.allocUnsafe(size);
- } catch (ex) {
- return ex;
- }
- }
- function read_(self, handle, buf, off, len, position, cb, req_) {
- const maxDataLen = self._maxReadLen;
- const overflow = Math.max(len - maxDataLen, 0);
- if (overflow)
- len = maxDataLen;
- /*
- uint32 id
- string handle
- uint64 offset
- uint32 len
- */
- const handleLen = handle.length;
- let p = 9;
- let pos = position;
- const out = Buffer.allocUnsafe(4 + 1 + 4 + 4 + handleLen + 8 + 4);
- writeUInt32BE(out, out.length - 4, 0);
- out[4] = REQUEST.READ;
- const reqid = self._writeReqid = (self._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(out, reqid, 5);
- writeUInt32BE(out, handleLen, p);
- out.set(handle, p += 4);
- p += handleLen;
- for (let i = 7; i >= 0; --i) {
- out[p + i] = pos & 0xFF;
- pos /= 256;
- }
- writeUInt32BE(out, len, p += 8);
- if (typeof cb !== 'function')
- cb = noop;
- const req = (req_ || {
- nb: 0,
- position,
- off,
- origOff: off,
- len: undefined,
- overflow: undefined,
- cb: (err, data, nb) => {
- const len = req.len;
- const overflow = req.overflow;
- if (err) {
- if (cb._wantEOFError || err.code !== STATUS_CODE.EOF)
- return cb(err);
- } else if (nb > len) {
- return cb(new Error('Received more data than requested'));
- } else if (nb === len && overflow) {
- req.nb += nb;
- req.position += nb;
- req.off += nb;
- read_(self, handle, buf, req.off, overflow, req.position, cb, req);
- return;
- }
- if (req.origOff === 0 && buf.length === req.nb)
- data = buf;
- else
- data = bufferSlice(buf, req.origOff, req.origOff + req.nb);
- cb(undefined, req.nb + (nb || 0), data, req.position);
- },
- buffer: undefined,
- });
- req.len = len;
- req.overflow = overflow;
- // TODO: avoid creating multiple buffer slices when we need to re-call read_()
- // because of overflow
- req.buffer = bufferSlice(buf, off, off + len);
- self._requests[reqid] = req;
- const isBuffered = sendOrBuffer(self, out);
- self._debug && self._debug(
- `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} READ`
- );
- }
- function fastXfer(src, dst, srcPath, dstPath, opts, cb) {
- let concurrency = 64;
- let chunkSize = 32768;
- let onstep;
- let mode;
- let fileSize;
- if (typeof opts === 'function') {
- cb = opts;
- } else if (typeof opts === 'object' && opts !== null) {
- 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.fileSize === 'number'
- && opts.fileSize > 0
- && !isNaN(opts.fileSize)) {
- fileSize = opts.fileSize;
- }
- if (typeof opts.step === 'function')
- onstep = opts.step;
- if (typeof opts.mode === 'string' || typeof opts.mode === 'number')
- mode = modeNum(opts.mode);
- }
- // Internal state variables
- let fsize;
- let pdst = 0;
- let total = 0;
- let hadError = false;
- let srcHandle;
- let dstHandle;
- let readbuf;
- let bufsize = chunkSize * concurrency;
- function onerror(err) {
- if (hadError)
- return;
- hadError = true;
- let left = 0;
- let cbfinal;
- if (srcHandle || dstHandle) {
- cbfinal = () => {
- if (--left === 0)
- cb(err);
- };
- if (srcHandle && (src === fs || src.outgoing.state === 'open'))
- ++left;
- if (dstHandle && (dst === fs || dst.outgoing.state === 'open'))
- ++left;
- if (srcHandle && (src === fs || src.outgoing.state === 'open'))
- src.close(srcHandle, cbfinal);
- if (dstHandle && (dst === fs || dst.outgoing.state === 'open'))
- dst.close(dstHandle, cbfinal);
- } else {
- cb(err);
- }
- }
- src.open(srcPath, 'r', (err, sourceHandle) => {
- if (err)
- return onerror(err);
- srcHandle = sourceHandle;
- if (fileSize === undefined)
- src.fstat(srcHandle, tryStat);
- else
- tryStat(null, { size: fileSize });
- 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, (err_, attrs_) => {
- if (err_)
- return onerror(err);
- tryStat(null, attrs_);
- });
- return;
- }
- return onerror(err);
- }
- fsize = attrs.size;
- dst.open(dstPath, 'w', (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, (err_) => tryAgain());
- return;
- }
- startReads();
- });
- } else {
- startReads();
- }
- function onread(err, nb, data, dstpos, datapos, origChunkLen) {
- if (err)
- return onerror(err);
- datapos = datapos || 0;
- dst.write(dstHandle, readbuf, datapos, nb, dstpos, writeCb);
- function writeCb(err) {
- if (err)
- return onerror(err);
- total += nb;
- onstep && onstep(total, nb, fsize);
- if (nb < origChunkLen)
- return singleRead(datapos, dstpos + nb, origChunkLen - nb);
- if (total === fsize) {
- dst.close(dstHandle, (err) => {
- dstHandle = undefined;
- if (err)
- return onerror(err);
- src.close(srcHandle, (err) => {
- srcHandle = undefined;
- if (err)
- return onerror(err);
- cb();
- });
- });
- return;
- }
- if (pdst >= fsize)
- return;
- const chunk =
- (pdst + chunkSize > fsize ? fsize - pdst : chunkSize);
- singleRead(datapos, pdst, chunk);
- pdst += chunk;
- }
- }
- function makeCb(psrc, pdst, chunk) {
- return (err, nb, data) => {
- onread(err, nb, data, pdst, psrc, chunk);
- };
- }
- function singleRead(psrc, pdst, chunk) {
- src.read(srcHandle,
- readbuf,
- psrc,
- chunk,
- pdst,
- makeCb(psrc, pdst, chunk));
- }
- function startReads() {
- let reads = 0;
- let psrc = 0;
- while (pdst < fsize && reads < concurrency) {
- const chunk =
- (pdst + chunkSize > fsize ? fsize - pdst : chunkSize);
- singleRead(psrc, pdst, chunk);
- psrc += chunk;
- pdst += chunk;
- ++reads;
- }
- }
- });
- }
- });
- }
- function writeAll(sftp, handle, buffer, offset, length, position, callback_) {
- const callback = (typeof callback_ === 'function' ? callback_ : undefined);
- sftp.write(handle,
- buffer,
- offset,
- length,
- position,
- (writeErr, written) => {
- if (writeErr) {
- return sftp.close(handle, () => {
- callback && callback(writeErr);
- });
- }
- if (written === length) {
- sftp.close(handle, callback);
- } else {
- offset += written;
- length -= written;
- position += written;
- writeAll(sftp, handle, buffer, offset, length, position, callback);
- }
- });
- }
- class Stats {
- constructor(initial) {
- this.mode = (initial && initial.mode);
- 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);
- this.extended = (initial && initial.extended);
- }
- isDirectory() {
- return ((this.mode & constants.S_IFMT) === constants.S_IFDIR);
- }
- isFile() {
- return ((this.mode & constants.S_IFMT) === constants.S_IFREG);
- }
- isBlockDevice() {
- return ((this.mode & constants.S_IFMT) === constants.S_IFBLK);
- }
- isCharacterDevice() {
- return ((this.mode & constants.S_IFMT) === constants.S_IFCHR);
- }
- isSymbolicLink() {
- return ((this.mode & constants.S_IFMT) === constants.S_IFLNK);
- }
- isFIFO() {
- return ((this.mode & constants.S_IFMT) === constants.S_IFIFO);
- }
- isSocket() {
- return ((this.mode & constants.S_IFMT) === constants.S_IFSOCK);
- }
- }
- function attrsToBytes(attrs) {
- let flags = 0;
- let nb = 0;
- if (typeof attrs === 'object' && attrs !== null) {
- if (typeof attrs.size === 'number') {
- flags |= ATTR.SIZE;
- const val = attrs.size;
- // Big Endian
- ATTRS_BUF[nb++] = val / 72057594037927940; // 2**56
- ATTRS_BUF[nb++] = val / 281474976710656; // 2**48
- ATTRS_BUF[nb++] = val / 1099511627776; // 2**40
- ATTRS_BUF[nb++] = val / 4294967296; // 2**32
- ATTRS_BUF[nb++] = val / 16777216; // 2**24
- ATTRS_BUF[nb++] = val / 65536; // 2**16
- ATTRS_BUF[nb++] = val / 256; // 2**8
- ATTRS_BUF[nb++] = val;
- }
- if (typeof attrs.uid === 'number' && typeof attrs.gid === 'number') {
- flags |= ATTR.UIDGID;
- const uid = attrs.uid;
- const gid = attrs.gid;
- // Big Endian
- ATTRS_BUF[nb++] = uid >>> 24;
- ATTRS_BUF[nb++] = uid >>> 16;
- ATTRS_BUF[nb++] = uid >>> 8;
- ATTRS_BUF[nb++] = uid;
- ATTRS_BUF[nb++] = gid >>> 24;
- ATTRS_BUF[nb++] = gid >>> 16;
- ATTRS_BUF[nb++] = gid >>> 8;
- ATTRS_BUF[nb++] = gid;
- }
- if (typeof attrs.mode === 'number' || typeof attrs.mode === 'string') {
- const mode = modeNum(attrs.mode);
- flags |= ATTR.PERMISSIONS;
- // Big Endian
- ATTRS_BUF[nb++] = mode >>> 24;
- ATTRS_BUF[nb++] = mode >>> 16;
- ATTRS_BUF[nb++] = mode >>> 8;
- ATTRS_BUF[nb++] = mode;
- }
- if ((typeof attrs.atime === 'number' || isDate(attrs.atime))
- && (typeof attrs.mtime === 'number' || isDate(attrs.mtime))) {
- const atime = toUnixTimestamp(attrs.atime);
- const mtime = toUnixTimestamp(attrs.mtime);
- flags |= ATTR.ACMODTIME;
- // Big Endian
- ATTRS_BUF[nb++] = atime >>> 24;
- ATTRS_BUF[nb++] = atime >>> 16;
- ATTRS_BUF[nb++] = atime >>> 8;
- ATTRS_BUF[nb++] = atime;
- ATTRS_BUF[nb++] = mtime >>> 24;
- ATTRS_BUF[nb++] = mtime >>> 16;
- ATTRS_BUF[nb++] = mtime >>> 8;
- ATTRS_BUF[nb++] = mtime;
- }
- // TODO: extended attributes
- }
- return { flags, nb };
- }
- function toUnixTimestamp(time) {
- // eslint-disable-next-line no-self-compare
- if (typeof time === 'number' && time === time) // Valid, non-NaN number
- return time;
- if (isDate(time))
- return parseInt(time.getTime() / 1000, 10);
- throw new Error(`Cannot parse time: ${time}`);
- }
- function modeNum(mode) {
- // eslint-disable-next-line no-self-compare
- if (typeof mode === 'number' && mode === mode) // Valid, non-NaN number
- return mode;
- if (typeof mode === 'string')
- return modeNum(parseInt(mode, 8));
- throw new Error(`Cannot parse mode: ${mode}`);
- }
- const 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
- };
- function stringToFlags(str) {
- const flags = stringFlagMap[str];
- return (flags !== undefined ? flags : null);
- }
- const flagsToString = (() => {
- const stringFlagMapKeys = Object.keys(stringFlagMap);
- return (flags) => {
- for (let i = 0; i < stringFlagMapKeys.length; ++i) {
- const key = stringFlagMapKeys[i];
- if (stringFlagMap[key] === flags)
- return key;
- }
- return null;
- };
- })();
- function readAttrs(biOpt) {
- /*
- 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
- */
- const flags = bufferParser.readUInt32BE();
- if (flags === undefined)
- return;
- const attrs = new Stats();
- if (flags & ATTR.SIZE) {
- const size = bufferParser.readUInt64BE(biOpt);
- if (size === undefined)
- return;
- attrs.size = size;
- }
- if (flags & ATTR.UIDGID) {
- const uid = bufferParser.readUInt32BE();
- const gid = bufferParser.readUInt32BE();
- if (gid === undefined)
- return;
- attrs.uid = uid;
- attrs.gid = gid;
- }
- if (flags & ATTR.PERMISSIONS) {
- const mode = bufferParser.readUInt32BE();
- if (mode === undefined)
- return;
- attrs.mode = mode;
- }
- if (flags & ATTR.ACMODTIME) {
- const atime = bufferParser.readUInt32BE();
- const mtime = bufferParser.readUInt32BE();
- if (mtime === undefined)
- return;
- attrs.atime = atime;
- attrs.mtime = mtime;
- }
- if (flags & ATTR.EXTENDED) {
- const count = bufferParser.readUInt32BE();
- if (count === undefined)
- return;
- const extended = {};
- for (let i = 0; i < count; ++i) {
- const type = bufferParser.readString(true);
- const data = bufferParser.readString();
- if (data === undefined)
- return;
- extended[type] = data;
- }
- attrs.extended = extended;
- }
- return attrs;
- }
- function sendOrBuffer(sftp, payload) {
- const ret = tryWritePayload(sftp, payload);
- if (ret !== undefined) {
- sftp._buffer.push(ret);
- return false;
- }
- return true;
- }
- function tryWritePayload(sftp, payload) {
- const outgoing = sftp.outgoing;
- if (outgoing.state !== 'open')
- return;
- if (outgoing.window === 0) {
- sftp._waitWindow = true; // XXX: Unnecessary?
- return payload;
- }
- let ret;
- const len = payload.length;
- let p = 0;
- while (len - p > 0 && outgoing.window > 0) {
- const actualLen = Math.min(len - p, outgoing.window, outgoing.packetSize);
- outgoing.window -= actualLen;
- if (outgoing.window === 0) {
- sftp._waitWindow = true;
- sftp._chunkcb = drainBuffer;
- }
- if (p === 0 && actualLen === len) {
- sftp._protocol.channelData(sftp.outgoing.id, payload);
- } else {
- sftp._protocol.channelData(sftp.outgoing.id,
- bufferSlice(payload, p, p + actualLen));
- }
- p += actualLen;
- }
- if (len - p > 0) {
- if (p > 0)
- ret = bufferSlice(payload, p, len);
- else
- ret = payload; // XXX: should never get here?
- }
- return ret;
- }
- function drainBuffer() {
- this._chunkcb = undefined;
- const buffer = this._buffer;
- let i = 0;
- while (i < buffer.length) {
- const payload = buffer[i];
- const ret = tryWritePayload(this, payload);
- if (ret !== undefined) {
- if (ret !== payload)
- buffer[i] = ret;
- if (i > 0)
- this._buffer = buffer.slice(i);
- return;
- }
- ++i;
- }
- if (i > 0)
- this._buffer = [];
- }
- function doFatalSFTPError(sftp, msg, noDebug) {
- const err = new Error(msg);
- err.level = 'sftp-protocol';
- if (!noDebug && sftp._debug)
- sftp._debug(`SFTP: Inbound: ${msg}`);
- sftp.emit('error', err);
- sftp.destroy();
- cleanupRequests(sftp);
- return false;
- }
- function cleanupRequests(sftp) {
- const keys = Object.keys(sftp._requests);
- if (keys.length === 0)
- return;
- const reqs = sftp._requests;
- sftp._requests = {};
- const err = new Error('No response from server');
- for (let i = 0; i < keys.length; ++i) {
- const req = reqs[keys[i]];
- if (typeof req.cb === 'function')
- req.cb(err);
- }
- }
- function requestLimits(sftp, cb) {
- /*
- uint32 id
- string "limits@openssh.com"
- */
- let p = 9;
- const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 18);
- writeUInt32BE(buf, buf.length - 4, 0);
- buf[4] = REQUEST.EXTENDED;
- const reqid = sftp._writeReqid = (sftp._writeReqid + 1) & MAX_REQID;
- writeUInt32BE(buf, reqid, 5);
- writeUInt32BE(buf, 18, p);
- buf.utf8Write('limits@openssh.com', p += 4, 18);
- sftp._requests[reqid] = { extended: 'limits@openssh.com', cb };
- const isBuffered = sendOrBuffer(sftp, buf);
- if (sftp._debug) {
- const which = (isBuffered ? 'Buffered' : 'Sending');
- sftp._debug(`SFTP: Outbound: ${which} limits@openssh.com`);
- }
- }
- const CLIENT_HANDLERS = {
- [RESPONSE.VERSION]: (sftp, payload) => {
- if (sftp._version !== -1)
- return doFatalSFTPError(sftp, 'Duplicate VERSION packet');
- const extensions = {};
- /*
- uint32 version
- <extension data>
- */
- bufferParser.init(payload, 1);
- let version = bufferParser.readUInt32BE();
- while (bufferParser.avail()) {
- const extName = bufferParser.readString(true);
- const extData = bufferParser.readString(true);
- if (extData === undefined) {
- version = undefined;
- break;
- }
- extensions[extName] = extData;
- }
- bufferParser.clear();
- if (version === undefined)
- return doFatalSFTPError(sftp, 'Malformed VERSION packet');
- if (sftp._debug) {
- const names = Object.keys(extensions);
- if (names.length) {
- sftp._debug(
- `SFTP: Inbound: Received VERSION (v${version}, exts:${names})`
- );
- } else {
- sftp._debug(`SFTP: Inbound: Received VERSION (v${version})`);
- }
- }
- sftp._version = version;
- sftp._extensions = extensions;
- if (extensions['limits@openssh.com'] === '1') {
- return requestLimits(sftp, (err, limits) => {
- if (!err) {
- if (limits.maxPktLen > 0)
- sftp._maxOutPktLen = limits.maxPktLen;
- if (limits.maxReadLen > 0)
- sftp._maxReadLen = limits.maxReadLen;
- if (limits.maxWriteLen > 0)
- sftp._maxWriteLen = limits.maxWriteLen;
- sftp.maxOpenHandles = (
- limits.maxOpenHandles > 0 ? limits.maxOpenHandles : Infinity
- );
- }
- sftp.emit('ready');
- });
- }
- sftp.emit('ready');
- },
- [RESPONSE.STATUS]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- uint32 error/status code
- string error message (ISO-10646 UTF-8)
- string language tag
- */
- const errorCode = bufferParser.readUInt32BE();
- const errorMsg = bufferParser.readString(true);
- const lang = bufferParser.skipString();
- bufferParser.clear();
- if (lang === undefined) {
- if (reqID !== undefined)
- delete sftp._requests[reqID];
- return doFatalSFTPError(sftp, 'Malformed STATUS packet');
- }
- if (sftp._debug) {
- const jsonMsg = JSON.stringify(errorMsg);
- sftp._debug(
- `SFTP: Inbound: Received STATUS (id:${reqID}, ${errorCode}, ${jsonMsg})`
- );
- }
- const req = sftp._requests[reqID];
- delete sftp._requests[reqID];
- if (req && typeof req.cb === 'function') {
- if (errorCode === STATUS_CODE.OK) {
- req.cb();
- return;
- }
- const err = new Error(errorMsg
- || STATUS_CODE_STR[errorCode]
- || 'Unknown status');
- err.code = errorCode;
- req.cb(err);
- }
- },
- [RESPONSE.HANDLE]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string handle
- */
- const handle = bufferParser.readString();
- bufferParser.clear();
- if (handle === undefined) {
- if (reqID !== undefined)
- delete sftp._requests[reqID];
- return doFatalSFTPError(sftp, 'Malformed HANDLE packet');
- }
- sftp._debug && sftp._debug(`SFTP: Inbound: Received HANDLE (id:${reqID})`);
- const req = sftp._requests[reqID];
- delete sftp._requests[reqID];
- if (req && typeof req.cb === 'function')
- req.cb(undefined, handle);
- },
- [RESPONSE.DATA]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- let req;
- if (reqID !== undefined) {
- req = sftp._requests[reqID];
- delete sftp._requests[reqID];
- }
- /*
- string data
- */
- if (req && typeof req.cb === 'function') {
- if (req.buffer) {
- // We have already pre-allocated space to store the data
- const nb = bufferParser.readString(req.buffer);
- bufferParser.clear();
- if (nb !== undefined) {
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received DATA (id:${reqID}, ${nb})`
- );
- req.cb(undefined, req.buffer, nb);
- return;
- }
- } else {
- const data = bufferParser.readString();
- bufferParser.clear();
- if (data !== undefined) {
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received DATA (id:${reqID}, ${data.length})`
- );
- req.cb(undefined, data);
- return;
- }
- }
- } else {
- const nb = bufferParser.skipString();
- bufferParser.clear();
- if (nb !== undefined) {
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received DATA (id:${reqID}, ${nb})`
- );
- return;
- }
- }
- return doFatalSFTPError(sftp, 'Malformed DATA packet');
- },
- [RESPONSE.NAME]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- let req;
- if (reqID !== undefined) {
- req = sftp._requests[reqID];
- delete sftp._requests[reqID];
- }
- /*
- uint32 count
- repeats count times:
- string filename
- string longname
- ATTRS attrs
- */
- const count = bufferParser.readUInt32BE();
- if (count !== undefined) {
- let names = [];
- for (let i = 0; i < count; ++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
- const filename = bufferParser.readString(true);
- // `longname` only exists in SFTPv3 and since it typically will
- // contain the filename, we assume it is also UTF-8
- const longname = bufferParser.readString(true);
- const attrs = readAttrs(sftp._biOpt);
- if (attrs === undefined) {
- names = undefined;
- break;
- }
- names.push({ filename, longname, attrs });
- }
- if (names !== undefined) {
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received NAME (id:${reqID}, ${names.length})`
- );
- bufferParser.clear();
- if (req && typeof req.cb === 'function')
- req.cb(undefined, names);
- return;
- }
- }
- bufferParser.clear();
- return doFatalSFTPError(sftp, 'Malformed NAME packet');
- },
- [RESPONSE.ATTRS]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- let req;
- if (reqID !== undefined) {
- req = sftp._requests[reqID];
- delete sftp._requests[reqID];
- }
- /*
- ATTRS attrs
- */
- const attrs = readAttrs(sftp._biOpt);
- bufferParser.clear();
- if (attrs !== undefined) {
- sftp._debug && sftp._debug(`SFTP: Inbound: Received ATTRS (id:${reqID})`);
- if (req && typeof req.cb === 'function')
- req.cb(undefined, attrs);
- return;
- }
- return doFatalSFTPError(sftp, 'Malformed ATTRS packet');
- },
- [RESPONSE.EXTENDED]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- if (reqID !== undefined) {
- const req = sftp._requests[reqID];
- if (req) {
- delete sftp._requests[reqID];
- 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
- */
- const biOpt = sftp._biOpt;
- const stats = {
- f_bsize: bufferParser.readUInt64BE(biOpt),
- f_frsize: bufferParser.readUInt64BE(biOpt),
- f_blocks: bufferParser.readUInt64BE(biOpt),
- f_bfree: bufferParser.readUInt64BE(biOpt),
- f_bavail: bufferParser.readUInt64BE(biOpt),
- f_files: bufferParser.readUInt64BE(biOpt),
- f_ffree: bufferParser.readUInt64BE(biOpt),
- f_favail: bufferParser.readUInt64BE(biOpt),
- f_sid: bufferParser.readUInt64BE(biOpt),
- f_flag: bufferParser.readUInt64BE(biOpt),
- f_namemax: bufferParser.readUInt64BE(biOpt),
- };
- if (stats.f_namemax === undefined)
- break;
- if (sftp._debug) {
- sftp._debug(
- 'SFTP: Inbound: Received EXTENDED_REPLY '
- + `(id:${reqID}, ${req.extended})`
- );
- }
- bufferParser.clear();
- if (typeof req.cb === 'function')
- req.cb(undefined, stats);
- return;
- }
- case 'limits@openssh.com': {
- /*
- uint64 max-packet-length
- uint64 max-read-length
- uint64 max-write-length
- uint64 max-open-handles
- */
- const limits = {
- maxPktLen: bufferParser.readUInt64BE(),
- maxReadLen: bufferParser.readUInt64BE(),
- maxWriteLen: bufferParser.readUInt64BE(),
- maxOpenHandles: bufferParser.readUInt64BE(),
- };
- if (limits.maxOpenHandles === undefined)
- break;
- if (sftp._debug) {
- sftp._debug(
- 'SFTP: Inbound: Received EXTENDED_REPLY '
- + `(id:${reqID}, ${req.extended})`
- );
- }
- bufferParser.clear();
- if (typeof req.cb === 'function')
- req.cb(undefined, limits);
- return;
- }
- default:
- // Unknown extended request
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received EXTENDED_REPLY (id:${reqID}, ???)`
- );
- bufferParser.clear();
- if (typeof req.cb === 'function')
- req.cb();
- return;
- }
- } else {
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received EXTENDED_REPLY (id:${reqID}, ???)`
- );
- bufferParser.clear();
- return;
- }
- }
- bufferParser.clear();
- return doFatalSFTPError(sftp, 'Malformed EXTENDED_REPLY packet');
- },
- };
- const SERVER_HANDLERS = {
- [REQUEST.INIT]: (sftp, payload) => {
- if (sftp._version !== -1)
- return doFatalSFTPError(sftp, 'Duplicate INIT packet');
- const extensions = {};
- /*
- uint32 version
- <extension data>
- */
- bufferParser.init(payload, 1);
- let version = bufferParser.readUInt32BE();
- while (bufferParser.avail()) {
- const extName = bufferParser.readString(true);
- const extData = bufferParser.readString(true);
- if (extData === undefined) {
- version = undefined;
- break;
- }
- extensions[extName] = extData;
- }
- bufferParser.clear();
- if (version === undefined)
- return doFatalSFTPError(sftp, 'Malformed INIT packet');
- if (sftp._debug) {
- const names = Object.keys(extensions);
- if (names.length) {
- sftp._debug(
- `SFTP: Inbound: Received INIT (v${version}, exts:${names})`
- );
- } else {
- sftp._debug(`SFTP: Inbound: Received INIT (v${version})`);
- }
- }
- sendOrBuffer(sftp, SERVER_VERSION_BUFFER);
- sftp._version = version;
- sftp._extensions = extensions;
- sftp.emit('ready');
- },
- [REQUEST.OPEN]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string filename
- uint32 pflags
- ATTRS attrs
- */
- const filename = bufferParser.readString(true);
- const pflags = bufferParser.readUInt32BE();
- const attrs = readAttrs(sftp._biOpt);
- bufferParser.clear();
- if (attrs === undefined)
- return doFatalSFTPError(sftp, 'Malformed OPEN packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received OPEN (id:${reqID})`);
- if (!sftp.emit('OPEN', reqID, filename, pflags, attrs)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.CLOSE]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string handle
- */
- const handle = bufferParser.readString();
- bufferParser.clear();
- if (handle === undefined || handle.length > 256)
- return doFatalSFTPError(sftp, 'Malformed CLOSE packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received CLOSE (id:${reqID})`);
- if (!sftp.emit('CLOSE', reqID, handle)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.READ]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string handle
- uint64 offset
- uint32 len
- */
- const handle = bufferParser.readString();
- const offset = bufferParser.readUInt64BE(sftp._biOpt);
- const len = bufferParser.readUInt32BE();
- bufferParser.clear();
- if (len === undefined || handle.length > 256)
- return doFatalSFTPError(sftp, 'Malformed READ packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received READ (id:${reqID})`);
- if (!sftp.emit('READ', reqID, handle, offset, len)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.WRITE]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string handle
- uint64 offset
- string data
- */
- const handle = bufferParser.readString();
- const offset = bufferParser.readUInt64BE(sftp._biOpt);
- const data = bufferParser.readString();
- bufferParser.clear();
- if (data === undefined || handle.length > 256)
- return doFatalSFTPError(sftp, 'Malformed WRITE packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received WRITE (id:${reqID})`);
- if (!sftp.emit('WRITE', reqID, handle, offset, data)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.LSTAT]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- */
- const path = bufferParser.readString(true);
- bufferParser.clear();
- if (path === undefined)
- return doFatalSFTPError(sftp, 'Malformed LSTAT packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received LSTAT (id:${reqID})`);
- if (!sftp.emit('LSTAT', reqID, path)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.FSTAT]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string handle
- */
- const handle = bufferParser.readString();
- bufferParser.clear();
- if (handle === undefined || handle.length > 256)
- return doFatalSFTPError(sftp, 'Malformed FSTAT packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received FSTAT (id:${reqID})`);
- if (!sftp.emit('FSTAT', reqID, handle)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.SETSTAT]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- ATTRS attrs
- */
- const path = bufferParser.readString(true);
- const attrs = readAttrs(sftp._biOpt);
- bufferParser.clear();
- if (attrs === undefined)
- return doFatalSFTPError(sftp, 'Malformed SETSTAT packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received SETSTAT (id:${reqID})`);
- if (!sftp.emit('SETSTAT', reqID, path, attrs)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.FSETSTAT]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string handle
- ATTRS attrs
- */
- const handle = bufferParser.readString();
- const attrs = readAttrs(sftp._biOpt);
- bufferParser.clear();
- if (attrs === undefined || handle.length > 256)
- return doFatalSFTPError(sftp, 'Malformed FSETSTAT packet');
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received FSETSTAT (id:${reqID})`
- );
- if (!sftp.emit('FSETSTAT', reqID, handle, attrs)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.OPENDIR]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- */
- const path = bufferParser.readString(true);
- bufferParser.clear();
- if (path === undefined)
- return doFatalSFTPError(sftp, 'Malformed OPENDIR packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received OPENDIR (id:${reqID})`);
- if (!sftp.emit('OPENDIR', reqID, path)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.READDIR]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string handle
- */
- const handle = bufferParser.readString();
- bufferParser.clear();
- if (handle === undefined || handle.length > 256)
- return doFatalSFTPError(sftp, 'Malformed READDIR packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received READDIR (id:${reqID})`);
- if (!sftp.emit('READDIR', reqID, handle)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.REMOVE]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- */
- const path = bufferParser.readString(true);
- bufferParser.clear();
- if (path === undefined)
- return doFatalSFTPError(sftp, 'Malformed REMOVE packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received REMOVE (id:${reqID})`);
- if (!sftp.emit('REMOVE', reqID, path)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.MKDIR]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- ATTRS attrs
- */
- const path = bufferParser.readString(true);
- const attrs = readAttrs(sftp._biOpt);
- bufferParser.clear();
- if (attrs === undefined)
- return doFatalSFTPError(sftp, 'Malformed MKDIR packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received MKDIR (id:${reqID})`);
- if (!sftp.emit('MKDIR', reqID, path, attrs)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.RMDIR]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- */
- const path = bufferParser.readString(true);
- bufferParser.clear();
- if (path === undefined)
- return doFatalSFTPError(sftp, 'Malformed RMDIR packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received RMDIR (id:${reqID})`);
- if (!sftp.emit('RMDIR', reqID, path)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.REALPATH]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- */
- const path = bufferParser.readString(true);
- bufferParser.clear();
- if (path === undefined)
- return doFatalSFTPError(sftp, 'Malformed REALPATH packet');
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received REALPATH (id:${reqID})`
- );
- if (!sftp.emit('REALPATH', reqID, path)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.STAT]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- */
- const path = bufferParser.readString(true);
- bufferParser.clear();
- if (path === undefined)
- return doFatalSFTPError(sftp, 'Malformed STAT packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received STAT (id:${reqID})`);
- if (!sftp.emit('STAT', reqID, path)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.RENAME]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string oldpath
- string newpath
- */
- const oldPath = bufferParser.readString(true);
- const newPath = bufferParser.readString(true);
- bufferParser.clear();
- if (newPath === undefined)
- return doFatalSFTPError(sftp, 'Malformed RENAME packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received RENAME (id:${reqID})`);
- if (!sftp.emit('RENAME', reqID, oldPath, newPath)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.READLINK]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string path
- */
- const path = bufferParser.readString(true);
- bufferParser.clear();
- if (path === undefined)
- return doFatalSFTPError(sftp, 'Malformed READLINK packet');
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received READLINK (id:${reqID})`
- );
- if (!sftp.emit('READLINK', reqID, path)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.SYMLINK]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string linkpath
- string targetpath
- */
- const linkPath = bufferParser.readString(true);
- const targetPath = bufferParser.readString(true);
- bufferParser.clear();
- if (targetPath === undefined)
- return doFatalSFTPError(sftp, 'Malformed SYMLINK packet');
- sftp._debug && sftp._debug(`SFTP: Inbound: Received SYMLINK (id:${reqID})`);
- let handled;
- if (sftp._isOpenSSH) {
- // OpenSSH has linkpath and targetpath positions switched
- handled = sftp.emit('SYMLINK', reqID, targetPath, linkPath);
- } else {
- handled = sftp.emit('SYMLINK', reqID, linkPath, targetPath);
- }
- if (!handled) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- [REQUEST.EXTENDED]: (sftp, payload) => {
- bufferParser.init(payload, 1);
- const reqID = bufferParser.readUInt32BE();
- /*
- string extended-request
- ... any request-specific data ...
- */
- const extName = bufferParser.readString(true);
- if (extName === undefined) {
- bufferParser.clear();
- return doFatalSFTPError(sftp, 'Malformed EXTENDED packet');
- }
- let extData;
- if (bufferParser.avail())
- extData = bufferParser.readRaw();
- bufferParser.clear();
- sftp._debug && sftp._debug(
- `SFTP: Inbound: Received EXTENDED (id:${reqID})`
- );
- if (!sftp.emit('EXTENDED', reqID, extName, extData)) {
- // Automatically reject request if no handler for request type
- sftp.status(reqID, STATUS_CODE.OP_UNSUPPORTED);
- }
- },
- };
- // =============================================================================
- // ReadStream/WriteStream-related ==============================================
- // =============================================================================
- const {
- ERR_INVALID_ARG_TYPE,
- ERR_OUT_OF_RANGE,
- validateNumber
- } = require('./node-fs-compat');
- const kMinPoolSpace = 128;
- let pool;
- // It can happen that we expect to read a large chunk of data, and reserve
- // a large chunk of the pool accordingly, but the read() call only filled
- // a portion of it. If a concurrently executing read() then uses the same pool,
- // the "reserved" portion cannot be used, so we allow it to be re-used as a
- // new pool later.
- const poolFragments = [];
- function allocNewPool(poolSize) {
- if (poolFragments.length > 0)
- pool = poolFragments.pop();
- else
- pool = Buffer.allocUnsafe(poolSize);
- pool.used = 0;
- }
- // Check the `this.start` and `this.end` of stream.
- function checkPosition(pos, name) {
- if (!Number.isSafeInteger(pos)) {
- validateNumber(pos, name);
- if (!Number.isInteger(pos))
- throw new ERR_OUT_OF_RANGE(name, 'an integer', pos);
- throw new ERR_OUT_OF_RANGE(name, '>= 0 and <= 2 ** 53 - 1', pos);
- }
- if (pos < 0)
- throw new ERR_OUT_OF_RANGE(name, '>= 0 and <= 2 ** 53 - 1', pos);
- }
- function roundUpToMultipleOf8(n) {
- return (n + 7) & ~7; // Align to 8 byte boundary.
- }
- function ReadStream(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);
- // A little bit bigger buffer and water marks by default
- if (options.highWaterMark === undefined)
- options.highWaterMark = 64 * 1024;
- // For backwards compat do not emit close on destroy.
- options.emitClose = false;
- options.autoDestroy = false; // Node 14 major change.
- ReadableStream.call(this, options);
- this.path = path;
- this.flags = options.flags === undefined ? 'r' : options.flags;
- this.mode = options.mode === undefined ? 0o666 : options.mode;
- this.start = options.start;
- this.end = options.end;
- this.autoClose = options.autoClose === undefined ? true : options.autoClose;
- this.pos = 0;
- this.bytesRead = 0;
- this.closed = false;
- this.handle = options.handle === undefined ? null : options.handle;
- this.sftp = sftp;
- this._opening = false;
- if (this.start !== undefined) {
- checkPosition(this.start, 'start');
- this.pos = this.start;
- }
- if (this.end === undefined) {
- this.end = Infinity;
- } else if (this.end !== Infinity) {
- checkPosition(this.end, 'end');
- if (this.start !== undefined && this.start > this.end) {
- throw new ERR_OUT_OF_RANGE(
- 'start',
- `<= "end" (here: ${this.end})`,
- this.start
- );
- }
- }
- this.on('end', function() {
- if (this.autoClose)
- this.destroy();
- });
- if (!Buffer.isBuffer(this.handle))
- this.open();
- }
- inherits(ReadStream, ReadableStream);
- ReadStream.prototype.open = function() {
- if (this._opening)
- return;
- this._opening = true;
- this.sftp.open(this.path, this.flags, this.mode, (er, handle) => {
- this._opening = false;
- if (er) {
- this.emit('error', er);
- if (this.autoClose)
- this.destroy();
- return;
- }
- this.handle = handle;
- this.emit('open', handle);
- this.emit('ready');
- // Start the flow of data.
- this.read();
- });
- };
- ReadStream.prototype._read = function(n) {
- if (!Buffer.isBuffer(this.handle))
- return this.once('open', () => this._read(n));
- // XXX: safe to remove this?
- if (this.destroyed)
- return;
- if (!pool || pool.length - pool.used < kMinPoolSpace) {
- // Discard the old pool.
- allocNewPool(this.readableHighWaterMark
- || 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.
- const thisPool = pool;
- let toRead = Math.min(pool.length - pool.used, n);
- const 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.
- this.sftp.read(this.handle,
- pool,
- pool.used,
- toRead,
- this.pos,
- (er, bytesRead) => {
- if (er) {
- this.emit('error', er);
- if (this.autoClose)
- this.destroy();
- return;
- }
- let b = null;
- // Now that we know how much data we have actually read, re-wind the
- // 'used' field if we can, and otherwise allow the remainder of our
- // reservation to be used as a new pool later.
- if (start + toRead === thisPool.used && thisPool === pool) {
- thisPool.used = roundUpToMultipleOf8(thisPool.used + bytesRead - toRead);
- } else {
- // Round down to the next lowest multiple of 8 to ensure the new pool
- // fragment start and end positions are aligned to an 8 byte boundary.
- const alignedEnd = (start + toRead) & ~7;
- const alignedStart = roundUpToMultipleOf8(start + bytesRead);
- if (alignedEnd - alignedStart >= kMinPoolSpace)
- poolFragments.push(thisPool.slice(alignedStart, alignedEnd));
- }
- if (bytesRead > 0) {
- this.bytesRead += bytesRead;
- b = thisPool.slice(start, start + bytesRead);
- }
- // Move the pool positions, and internal position for reading.
- this.pos += bytesRead;
- this.push(b);
- });
- pool.used = roundUpToMultipleOf8(pool.used + toRead);
- };
- ReadStream.prototype._destroy = function(err, cb) {
- if (this._opening && !Buffer.isBuffer(this.handle)) {
- this.once('open', closeStream.bind(null, this, cb, err));
- return;
- }
- closeStream(this, cb, err);
- this.handle = null;
- this._opening = false;
- };
- function closeStream(stream, cb, err) {
- if (!stream.handle)
- return onclose();
- stream.sftp.close(stream.handle, onclose);
- function onclose(er) {
- er = er || err;
- cb(er);
- stream.closed = true;
- if (!er)
- stream.emit('close');
- }
- }
- ReadStream.prototype.close = function(cb) {
- this.destroy(null, cb);
- };
- Object.defineProperty(ReadStream.prototype, 'pending', {
- get() {
- return this.handle === null;
- },
- configurable: true
- });
- // TODO: add `concurrency` setting to allow more than one in-flight WRITE
- // request to server to improve throughput
- function 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);
- // For backwards compat do not emit close on destroy.
- options.emitClose = false;
- options.autoDestroy = false; // Node 14 major change.
- WritableStream.call(this, options);
- this.path = path;
- this.flags = options.flags === undefined ? 'w' : options.flags;
- this.mode = options.mode === undefined ? 0o666 : options.mode;
- this.start = options.start;
- this.autoClose = options.autoClose === undefined ? true : options.autoClose;
- this.pos = 0;
- this.bytesWritten = 0;
- this.closed = false;
- this.handle = options.handle === undefined ? null : options.handle;
- this.sftp = sftp;
- this._opening = false;
- if (this.start !== undefined) {
- checkPosition(this.start, 'start');
- this.pos = this.start;
- }
- if (options.encoding)
- this.setDefaultEncoding(options.encoding);
- // Node v6.x only
- this.on('finish', function() {
- if (this._writableState.finalCalled)
- return;
- if (this.autoClose)
- this.destroy();
- });
- if (!Buffer.isBuffer(this.handle))
- this.open();
- }
- inherits(WriteStream, WritableStream);
- WriteStream.prototype._final = function(cb) {
- if (this.autoClose)
- this.destroy();
- cb();
- };
- WriteStream.prototype.open = function() {
- if (this._opening)
- return;
- this._opening = true;
- this.sftp.open(this.path, this.flags, this.mode, (er, handle) => {
- this._opening = false;
- if (er) {
- this.emit('error', er);
- if (this.autoClose)
- this.destroy();
- return;
- }
- this.handle = handle;
- const tryAgain = (err) => {
- if (err) {
- // Try chmod() for sftp servers that may not support fchmod() for
- // whatever reason
- this.sftp.chmod(this.path, this.mode, (err_) => tryAgain());
- return;
- }
- // SFTPv3 requires absolute offsets, no matter the open flag used
- if (this.flags[0] === 'a') {
- const tryStat = (err, st) => {
- if (err) {
- // Try stat() for sftp servers that may not support fstat() for
- // whatever reason
- this.sftp.stat(this.path, (err_, st_) => {
- if (err_) {
- this.destroy();
- this.emit('error', err);
- return;
- }
- tryStat(null, st_);
- });
- return;
- }
- this.pos = st.size;
- this.emit('open', handle);
- this.emit('ready');
- };
- this.sftp.fstat(handle, tryStat);
- return;
- }
- this.emit('open', handle);
- this.emit('ready');
- };
- this.sftp.fchmod(handle, this.mode, tryAgain);
- });
- };
- WriteStream.prototype._write = function(data, encoding, cb) {
- if (!Buffer.isBuffer(data)) {
- const err = new ERR_INVALID_ARG_TYPE('data', 'Buffer', data);
- return this.emit('error', err);
- }
- if (!Buffer.isBuffer(this.handle)) {
- return this.once('open', function() {
- this._write(data, encoding, cb);
- });
- }
- this.sftp.write(this.handle,
- data,
- 0,
- data.length,
- this.pos,
- (er, bytes) => {
- if (er) {
- if (this.autoClose)
- this.destroy();
- return cb(er);
- }
- this.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);
- });
- }
- const sftp = this.sftp;
- const handle = this.handle;
- let writesLeft = data.length;
- const onwrite = (er, bytes) => {
- if (er) {
- this.destroy();
- return cb(er);
- }
- this.bytesWritten += bytes;
- if (--writesLeft === 0)
- cb();
- };
- // TODO: try to combine chunks to reduce number of requests to the server?
- for (let i = 0; i < data.length; ++i) {
- const chunk = data[i].chunk;
- sftp.write(handle, chunk, 0, chunk.length, this.pos, onwrite);
- this.pos += chunk.length;
- }
- };
- if (typeof WritableStream.prototype.destroy !== 'function')
- WriteStream.prototype.destroy = ReadStream.prototype.destroy;
- WriteStream.prototype._destroy = ReadStream.prototype._destroy;
- WriteStream.prototype.close = function(cb) {
- if (cb) {
- if (this.closed) {
- process.nextTick(cb);
- return;
- }
- this.on('close', cb);
- }
- // If we are not autoClosing, we should call
- // destroy on 'finish'.
- if (!this.autoClose)
- this.on('finish', this.destroy.bind(this));
- this.end();
- };
- // There is no shutdown() for files.
- WriteStream.prototype.destroySoon = WriteStream.prototype.end;
- Object.defineProperty(WriteStream.prototype, 'pending', {
- get() {
- return this.handle === null;
- },
- configurable: true
- });
- // =============================================================================
- module.exports = {
- flagsToString,
- OPEN_MODE,
- SFTP,
- Stats,
- STATUS_CODE,
- stringToFlags,
- };
|