Guest User

Untitled

a guest
Nov 6th, 2017
224
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.96 KB | None | 0 0
  1. var fs = require('fs'),
  2. tls = require('tls'),
  3. zlib = require('zlib'),
  4. Socket = require('net').Socket,
  5. EventEmitter = require('events').EventEmitter,
  6. inherits = require('util').inherits,
  7. inspect = require('util').inspect;
  8.  
  9. var Parser = require('./parser');
  10. var XRegExp = require('xregexp').XRegExp;
  11.  
  12. var REX_TIMEVAL = XRegExp.cache('^(?<year>\\d{4})(?<month>\\d{2})(?<date>\\d{2})(?<hour>\\d{2})(?<minute>\\d{2})(?<second>\\d+)(?:.\\d+)?$'),
  13. RE_PASV = /([\d]+),([\d]+),([\d]+),([\d]+),([-\d]+),([-\d]+)/,
  14. RE_EOL = /\r?\n/g,
  15. RE_WD = /"(.+)"(?: |$)/,
  16. RE_SYST = /^([^ ]+)(?: |$)/;
  17.  
  18. var /*TYPE = {
  19. SYNTAX: 0,
  20. INFO: 1,
  21. SOCKETS: 2,
  22. AUTH: 3,
  23. UNSPEC: 4,
  24. FILESYS: 5
  25. },*/
  26. RETVAL = {
  27. PRELIM: 1,
  28. OK: 2,
  29. WAITING: 3,
  30. ERR_TEMP: 4,
  31. ERR_PERM: 5
  32. },
  33. /*ERRORS = {
  34. 421: 'Service not available, closing control connection',
  35. 425: 'Can\'t open data connection',
  36. 426: 'Connection closed; transfer aborted',
  37. 450: 'Requested file action not taken / File unavailable (e.g., file busy)',
  38. 451: 'Requested action aborted: local error in processing',
  39. 452: 'Requested action not taken / Insufficient storage space in system',
  40. 500: 'Syntax error / Command unrecognized',
  41. 501: 'Syntax error in parameters or arguments',
  42. 502: 'Command not implemented',
  43. 503: 'Bad sequence of commands',
  44. 504: 'Command not implemented for that parameter',
  45. 530: 'Not logged in',
  46. 532: 'Need account for storing files',
  47. 550: 'Requested action not taken / File unavailable (e.g., file not found, no access)',
  48. 551: 'Requested action aborted: page type unknown',
  49. 552: 'Requested file action aborted / Exceeded storage allocation (for current directory or dataset)',
  50. 553: 'Requested action not taken / File name not allowed'
  51. },*/
  52. bytesNOOP = new Buffer('NOOP\r\n');
  53.  
  54. var FTP = module.exports = function() {
  55. if (!(this instanceof FTP))
  56. return new FTP();
  57.  
  58. this._socket = undefined;
  59. this._pasvSock = undefined;
  60. this._feat = undefined;
  61. this._curReq = undefined;
  62. this._queue = [];
  63. this._secstate = undefined;
  64. this._debug = undefined;
  65. this._keepalive = undefined;
  66. this._ending = false;
  67. this._parser = undefined;
  68. this.options = {
  69. host: undefined,
  70. port: undefined,
  71. user: undefined,
  72. password: undefined,
  73. secure: false,
  74. secureOptions: undefined,
  75. connTimeout: undefined,
  76. pasvTimeout: undefined,
  77. aliveTimeout: undefined
  78. };
  79. this.connected = false;
  80. };
  81. inherits(FTP, EventEmitter);
  82.  
  83. FTP.prototype.connect = function(options) {
  84. var self = this;
  85. if (typeof options !== 'object')
  86. options = {};
  87. this.connected = false;
  88. this.options.host = options.host || 'localhost';
  89. this.options.port = options.port || 21;
  90. this.options.user = options.user || 'anonymous';
  91. this.options.password = options.password || 'anonymous@';
  92. this.options.secure = options.secure || false;
  93. this.options.secureOptions = options.secureOptions;
  94. this.options.connTimeout = options.connTimeout || 10000;
  95. this.options.pasvTimeout = options.pasvTimeout || 10000;
  96. this.options.aliveTimeout = options.keepalive || 10000;
  97.  
  98. if (typeof options.debug === 'function')
  99. this._debug = options.debug;
  100.  
  101. var secureOptions,
  102. debug = this._debug,
  103. socket = new Socket();
  104.  
  105. socket.setTimeout(0);
  106. socket.setKeepAlive(true);
  107.  
  108. this._parser = new Parser({ debug: debug });
  109. this._parser.on('response', function(code, text) {
  110. var retval = code / 100 >> 0;
  111. if (retval === RETVAL.ERR_TEMP || retval === RETVAL.ERR_PERM) {
  112. if (self._curReq)
  113. self._curReq.cb(makeError(code, text), undefined, code);
  114. else
  115. self.emit('error', makeError(code, text));
  116. } else if (self._curReq)
  117. self._curReq.cb(undefined, text, code);
  118.  
  119. // a hack to signal we're waiting for a PASV data connection to complete
  120. // first before executing any more queued requests ...
  121. //
  122. // also: don't forget our current request if we're expecting another
  123. // terminating response ....
  124. if (self._curReq && retval !== RETVAL.PRELIM) {
  125. self._curReq = undefined;
  126. self._send();
  127. }
  128.  
  129. noopreq.cb();
  130. });
  131.  
  132. if (this.options.secure) {
  133. secureOptions = {};
  134. secureOptions.host = this.options.host;
  135. for (var k in this.options.secureOptions)
  136. secureOptions[k] = this.options.secureOptions[k];
  137. secureOptions.socket = socket;
  138. this.options.secureOptions = secureOptions;
  139. }
  140.  
  141. if (this.options.secure === 'implicit')
  142. this._socket = tls.connect(secureOptions, onconnect);
  143. else {
  144. socket.once('connect', onconnect);
  145. this._socket = socket;
  146. }
  147.  
  148. var noopreq = {
  149. cmd: 'NOOP',
  150. cb: function() {
  151. clearTimeout(self._keepalive);
  152. self._keepalive = setTimeout(donoop, self.options.aliveTimeout);
  153. }
  154. };
  155.  
  156. function donoop() {
  157. if (!self._socket || !self._socket.writable)
  158. clearTimeout(self._keepalive);
  159. else if (!self._curReq && self._queue.length === 0) {
  160. self._curReq = noopreq;
  161. debug&&debug('[connection] > NOOP');
  162. self._socket.write(bytesNOOP);
  163. } else
  164. noopreq.cb();
  165. }
  166.  
  167. function onconnect() {
  168. clearTimeout(timer);
  169. clearTimeout(self._keepalive);
  170. self.connected = true;
  171. self._socket = socket; // re-assign for implicit secure connections
  172.  
  173. var cmd;
  174.  
  175. if (self._secstate) {
  176. if (self._secstate === 'upgraded-tls' && self.options.secure === true) {
  177. cmd = 'PBSZ';
  178. self._send('PBSZ 0', reentry, true);
  179. } else {
  180. cmd = 'USER';
  181. self._send('USER ' + self.options.user, reentry, true);
  182. }
  183. } else {
  184. self._curReq = {
  185. cmd: '',
  186. cb: reentry
  187. };
  188. }
  189.  
  190. function reentry(err, text, code) {
  191. if (err && (!cmd || cmd === 'USER' || cmd === 'PASS' || cmd === 'TYPE')) {
  192. self.emit('error', err);
  193. return self._socket && self._socket.end();
  194. }
  195. if ((cmd === 'AUTH TLS' && code !== 234 && self.options.secure !== true)
  196. || (cmd === 'AUTH SSL' && code !== 334)
  197. || (cmd === 'PBSZ' && code !== 200)
  198. || (cmd === 'PROT' && code !== 200)) {
  199. self.emit('error', makeError(code, 'Unable to secure connection(s)'));
  200. return self._socket && self._socket.end();
  201. }
  202.  
  203. if (!cmd) {
  204. // sometimes the initial greeting can contain useful information
  205. // about authorized use, other limits, etc.
  206. self.emit('greeting', text);
  207.  
  208. if (self.options.secure && self.options.secure !== 'implicit') {
  209. cmd = 'AUTH TLS';
  210. self._send(cmd, reentry, true);
  211. } else {
  212. cmd = 'USER';
  213. self._send('USER ' + self.options.user, reentry, true);
  214. }
  215. } else if (cmd === 'USER') {
  216. if (code !== 230) {
  217. // password required
  218. if (!self.options.password) {
  219. self.emit('error', makeError(code, 'Password required'));
  220. return self._socket && self._socket.end();
  221. }
  222. cmd = 'PASS';
  223. self._send('PASS ' + self.options.password, reentry, true);
  224. } else {
  225. // no password required
  226. cmd = 'PASS';
  227. reentry(undefined, text, code);
  228. }
  229. } else if (cmd === 'PASS') {
  230. cmd = 'FEAT';
  231. self._send(cmd, reentry, true);
  232. } else if (cmd === 'FEAT') {
  233. if (!err)
  234. self._feat = Parser.parseFeat(text);
  235. cmd = 'TYPE';
  236. self._send('TYPE I', reentry, true);
  237. } else if (cmd === 'TYPE')
  238. self.emit('ready');
  239. else if (cmd === 'PBSZ') {
  240. cmd = 'PROT';
  241. self._send('PROT P', reentry, true);
  242. } else if (cmd === 'PROT') {
  243. cmd = 'USER';
  244. self._send('USER ' + self.options.user, reentry, true);
  245. } else if (cmd.substr(0, 4) === 'AUTH') {
  246. if (cmd === 'AUTH TLS' && code !== 234) {
  247. cmd = 'AUTH SSL';
  248. return self._send(cmd, reentry, true);
  249. } else if (cmd === 'AUTH TLS')
  250. self._secstate = 'upgraded-tls';
  251. else if (cmd === 'AUTH SSL')
  252. self._secstate = 'upgraded-ssl';
  253. socket.removeAllListeners('data');
  254. socket.removeAllListeners('error');
  255. socket._decoder = null;
  256. self._curReq = null; // prevent queue from being processed during
  257. // TLS/SSL negotiation
  258. secureOptions.socket = self._socket;
  259. secureOptions.session = undefined;
  260. socket = tls.connect(secureOptions, onconnect);
  261. socket.setEncoding('binary');
  262. socket.on('data', ondata);
  263. socket.once('end', onend);
  264. socket.on('error', onerror);
  265. }
  266. }
  267. }
  268.  
  269. socket.on('data', ondata);
  270. function ondata(chunk) {
  271. debug&&debug('[connection] < ' + inspect(chunk.toString('binary')));
  272. if (self._parser)
  273. self._parser.write(chunk);
  274. }
  275.  
  276. socket.on('error', onerror);
  277. function onerror(err) {
  278. clearTimeout(timer);
  279. clearTimeout(self._keepalive);
  280. self.emit('error', err);
  281. }
  282.  
  283. socket.once('end', onend);
  284. function onend() {
  285. ondone();
  286. self.emit('end');
  287. }
  288.  
  289. socket.once('close', function(had_err) {
  290. ondone();
  291. self.emit('close', had_err);
  292. });
  293.  
  294. var hasReset = false;
  295. function ondone() {
  296. if (!hasReset) {
  297. hasReset = true;
  298. clearTimeout(timer);
  299. self._reset();
  300. }
  301. }
  302.  
  303. var timer = setTimeout(function() {
  304. self.emit('error', new Error('Timeout while connecting to server'));
  305. self._socket && self._socket.destroy();
  306. self._reset();
  307. }, this.options.connTimeout);
  308.  
  309. this._socket.connect(this.options.port, this.options.host);
  310. };
  311.  
  312. FTP.prototype.end = function() {
  313. if (this._queue.length)
  314. this._ending = true;
  315. else
  316. this._reset();
  317. };
  318.  
  319. FTP.prototype.destroy = function() {
  320. this._reset();
  321. };
  322.  
  323. // "Standard" (RFC 959) commands
  324. FTP.prototype.ascii = function(cb) {
  325. return this._send('TYPE A', cb);
  326. };
  327.  
  328. FTP.prototype.binary = function(cb) {
  329. return this._send('TYPE I', cb);
  330. };
  331.  
  332. FTP.prototype.abort = function(immediate, cb) {
  333. if (typeof immediate === 'function') {
  334. cb = immediate;
  335. immediate = true;
  336. }
  337. if (immediate)
  338. this._send('ABOR', cb, true);
  339. else
  340. this._send('ABOR', cb);
  341. };
  342.  
  343. FTP.prototype.cwd = function(path, cb, promote) {
  344. this._send('CWD ' + path, function(err, text, code) {
  345. if (err)
  346. return cb(err);
  347. var m = RE_WD.exec(text);
  348. cb(undefined, m ? m[1] : undefined);
  349. }, promote);
  350. };
  351.  
  352. FTP.prototype.delete = function(path, cb) {
  353. this._send('DELE ' + path, cb);
  354. };
  355.  
  356. FTP.prototype.site = function(cmd, cb) {
  357. this._send('SITE ' + cmd, cb);
  358. };
  359.  
  360. FTP.prototype.status = function(cb) {
  361. this._send('STAT', cb);
  362. };
  363.  
  364. FTP.prototype.rename = function(from, to, cb) {
  365. var self = this;
  366. this._send('RNFR ' + from, function(err) {
  367. if (err)
  368. return cb(err);
  369.  
  370. self._send('RNTO ' + to, cb, true);
  371. });
  372. };
  373.  
  374. FTP.prototype.logout = function(cb) {
  375. this._send('QUIT', cb);
  376. };
  377.  
  378. FTP.prototype.listSafe = function(path, zcomp, cb) {
  379. if (typeof path === 'string') {
  380. var self = this;
  381. // store current path
  382. this.pwd(function(err, origpath) {
  383. if (err) return cb(err);
  384. // change to destination path
  385. self.cwd(path, function(err) {
  386. if (err) return cb(err);
  387. // get dir listing
  388. self.list(zcomp || false, function(err, list) {
  389. // change back to original path
  390. if (err) return self.cwd(origpath, cb);
  391. self.cwd(origpath, function(err) {
  392. if (err) return cb(err);
  393. cb(err, list);
  394. });
  395. });
  396. });
  397. });
  398. } else
  399. this.list(path, zcomp, cb);
  400. };
  401.  
  402. FTP.prototype.list = function(path, zcomp, cb) {
  403. var self = this, cmd;
  404.  
  405. if (typeof path === 'function') {
  406. // list(function() {})
  407. cb = path;
  408. path = undefined;
  409. cmd = 'LIST';
  410. zcomp = false;
  411. } else if (typeof path === 'boolean') {
  412. // list(true, function() {})
  413. cb = zcomp;
  414. zcomp = path;
  415. path = undefined;
  416. cmd = 'LIST';
  417. } else if (typeof zcomp === 'function') {
  418. // list('/foo', function() {})
  419. cb = zcomp;
  420. cmd = 'LIST ' + path;
  421. zcomp = false;
  422. } else
  423. cmd = 'LIST ' + path;
  424.  
  425. this._pasv(function(err, sock) {
  426. if (err)
  427. return cb(err);
  428.  
  429. if (self._queue[0] && self._queue[0].cmd === 'ABOR') {
  430. sock.destroy();
  431. return cb();
  432. }
  433.  
  434. var sockerr, done = false, replies = 0, entries, buffer = [], source = sock;
  435.  
  436. if (zcomp) {
  437. source = zlib.createInflate();
  438. sock.pipe(source);
  439. }
  440.  
  441. source.on('data', function(chunk) { buffer.push(chunk); });
  442. source.once('error', function(err) {
  443. if (!sock.aborting)
  444. sockerr = err;
  445. });
  446. source.once('end', ondone);
  447. source.once('close', ondone);
  448.  
  449. function ondone() {
  450. done = true;
  451. final();
  452. }
  453. function final() {
  454. if (done && replies === 2) {
  455. buffer = Buffer.concat(buffer).toString();
  456. replies = 3;
  457. if (sockerr)
  458. return cb(new Error('Unexpected data connection error: ' + sockerr));
  459. if (sock.aborting)
  460. return cb();
  461.  
  462. // process received data
  463. entries = buffer.split(RE_EOL);
  464. entries.pop(); // ending EOL
  465. var parsed = [];
  466. for (var i = 0, len = entries.length; i < len; ++i) {
  467. var parsedVal = Parser.parseListEntry(entries[i]);
  468. if (parsedVal !== null)
  469. parsed.push(parsedVal);
  470. }
  471.  
  472. if (zcomp) {
  473. self._send('MODE S', function() {
  474. cb(undefined, parsed);
  475. }, true);
  476. } else
  477. cb(undefined, parsed);
  478. }
  479. }
  480.  
  481. if (zcomp) {
  482. self._send('MODE Z', function(err, text, code) {
  483. if (err) {
  484. sock.destroy();
  485. return cb(makeError(code, 'Compression not supported'));
  486. }
  487. sendList();
  488. }, true);
  489. } else
  490. sendList();
  491.  
  492. function sendList() {
  493. // this callback will be executed multiple times, the first is when server
  494. // replies with 150 and then a final reply to indicate whether the
  495. // transfer was actually a success or not
  496. self._send(cmd, function(err, text, code) {
  497. if (err) {
  498. sock.destroy();
  499. if (zcomp) {
  500. self._send('MODE S', function() {
  501. cb(err);
  502. }, true);
  503. } else
  504. cb(err);
  505. return;
  506. }
  507.  
  508. // some servers may not open a data connection for empty directories
  509. if (++replies === 1 && code === 226) {
  510. replies = 2;
  511. sock.destroy();
  512. final();
  513. } else if (replies === 2)
  514. final();
  515. }, true);
  516. }
  517. });
  518. };
  519.  
  520. FTP.prototype.get = function(path, zcomp, cb) {
  521. var self = this;
  522. if (typeof zcomp === 'function') {
  523. cb = zcomp;
  524. zcomp = false;
  525. }
  526.  
  527. this._pasv(function(err, sock) {
  528. if (err)
  529. return cb(err);
  530.  
  531. if (self._queue[0] && self._queue[0].cmd === 'ABOR') {
  532. sock.destroy();
  533. return cb();
  534. }
  535.  
  536. // modify behavior of socket events so that we can emit 'error' once for
  537. // either a TCP-level error OR an FTP-level error response that we get when
  538. // the socket is closed (e.g. the server ran out of space).
  539. var sockerr, started = false, lastreply = false, done = false,
  540. source = sock;
  541.  
  542. if (zcomp) {
  543. source = zlib.createInflate();
  544. sock.pipe(source);
  545. sock._emit = sock.emit;
  546. sock.emit = function(ev, arg1) {
  547. if (ev === 'error') {
  548. if (!sockerr)
  549. sockerr = arg1;
  550. return;
  551. }
  552. sock._emit.apply(sock, Array.prototype.slice.call(arguments));
  553. };
  554. }
  555.  
  556. source._emit = source.emit;
  557. source.emit = function(ev, arg1) {
  558. if (ev === 'error') {
  559. if (!sockerr)
  560. sockerr = arg1;
  561. return;
  562. } else if (ev === 'end' || ev === 'close') {
  563. if (!done) {
  564. done = true;
  565. ondone();
  566. }
  567. return;
  568. }
  569. source._emit.apply(source, Array.prototype.slice.call(arguments));
  570. };
  571.  
  572. function ondone() {
  573. if (done && lastreply) {
  574. self._send('MODE S', function() {
  575. source._emit('end');
  576. source._emit('close');
  577. }, true);
  578. }
  579. }
  580.  
  581. sock.pause();
  582.  
  583. if (zcomp) {
  584. self._send('MODE Z', function(err, text, code) {
  585. if (err) {
  586. sock.destroy();
  587. return cb(makeError(code, 'Compression not supported'));
  588. }
  589. sendRetr();
  590. }, true);
  591. } else
  592. sendRetr();
  593.  
  594. function sendRetr() {
  595. // this callback will be executed multiple times, the first is when server
  596. // replies with 150, then a final reply after the data connection closes
  597. // to indicate whether the transfer was actually a success or not
  598. self._send('RETR ' + path, function(err, text, code) {
  599. if (sockerr || err) {
  600. sock.destroy();
  601. if (!started) {
  602. if (zcomp) {
  603. self._send('MODE S', function() {
  604. cb(sockerr || err);
  605. }, true);
  606. } else
  607. cb(sockerr || err);
  608. } else {
  609. source._emit('error', sockerr || err);
  610. source._emit('close', true);
  611. }
  612. return;
  613. }
  614. // server returns 125 when data connection is already open; we treat it
  615. // just like a 150
  616. if (code === 150 || code === 125) {
  617. started = true;
  618. cb(undefined, source);
  619. } else {
  620. lastreply = true;
  621. ondone();
  622. }
  623. }, true);
  624. }
  625. });
  626. };
  627.  
  628. FTP.prototype.put = function(input, path, zcomp, cb) {
  629. this._store('STOR ' + path, input, zcomp, cb);
  630. };
  631.  
  632. FTP.prototype.append = function(input, path, zcomp, cb) {
  633. this._store('APPE ' + path, input, zcomp, cb);
  634. };
  635.  
  636. FTP.prototype.pwd = function(cb) { // PWD is optional
  637. var self = this;
  638. this._send('PWD', function(err, text, code) {
  639. if (code === 502) {
  640. return self.cwd('.', function(cwderr, cwd) {
  641. if (cwderr)
  642. return cb(cwderr);
  643. if (cwd === undefined)
  644. cb(err);
  645. else
  646. cb(undefined, cwd);
  647. }, true);
  648. } else if (err)
  649. return cb(err);
  650. cb(undefined, RE_WD.exec(text)[1]);
  651. });
  652. };
  653.  
  654. FTP.prototype.cdup = function(cb) { // CDUP is optional
  655. var self = this;
  656. this._send('CDUP', function(err, text, code) {
  657. if (code === 502)
  658. self.cwd('..', cb, true);
  659. else
  660. cb(err);
  661. });
  662. };
  663.  
  664. FTP.prototype.mkdir = function(path, recursive, cb) { // MKD is optional
  665. if (typeof recursive === 'function') {
  666. cb = recursive;
  667. recursive = false;
  668. }
  669. if (!recursive)
  670. this._send('MKD ' + path, cb);
  671. else {
  672. var self = this, owd, abs, dirs, dirslen, i = -1, searching = true;
  673.  
  674. abs = (path[0] === '/');
  675.  
  676. var nextDir = function() {
  677. if (++i === dirslen) {
  678. // return to original working directory
  679. return self._send('CWD ' + owd, cb, true);
  680. }
  681. if (searching) {
  682. self._send('CWD ' + dirs[i], function(err, text, code) {
  683. if (code === 550) {
  684. searching = false;
  685. --i;
  686. } else if (err) {
  687. // return to original working directory
  688. return self._send('CWD ' + owd, function() {
  689. cb(err);
  690. }, true);
  691. }
  692. nextDir();
  693. }, true);
  694. } else {
  695. self._send('MKD ' + dirs[i], function(err, text, code) {
  696. if (err) {
  697. // return to original working directory
  698. return self._send('CWD ' + owd, function() {
  699. cb(err);
  700. }, true);
  701. }
  702. self._send('CWD ' + dirs[i], nextDir, true);
  703. }, true);
  704. }
  705. };
  706. this.pwd(function(err, cwd) {
  707. if (err)
  708. return cb(err);
  709. owd = cwd;
  710. if (abs)
  711. path = path.substr(1);
  712. if (path[path.length - 1] === '/')
  713. path = path.substring(0, path.length - 1);
  714. dirs = path.split('/');
  715. dirslen = dirs.length;
  716. if (abs)
  717. self._send('CWD /', function(err) {
  718. if (err)
  719. return cb(err);
  720. nextDir();
  721. }, true);
  722. else
  723. nextDir();
  724. });
  725. }
  726. };
  727.  
  728. FTP.prototype.rmdir = function(path, recursive, cb) { // RMD is optional
  729. if (typeof recursive === 'function') {
  730. cb = recursive;
  731. recursive = false;
  732. }
  733. if (!recursive) {
  734. return this._send('RMD ' + path, cb);
  735. }
  736.  
  737. var self = this;
  738. this.list(path, function(err, list) {
  739. if (err) return cb(err);
  740. var idx = 0;
  741.  
  742. // this function will be called once per listing entry
  743. var deleteNextEntry;
  744. deleteNextEntry = function(err) {
  745. if (err) return cb(err);
  746. if (idx >= list.length) {
  747. if (list[0] && list[0].name === path) {
  748. return cb(null);
  749. } else {
  750. return self.rmdir(path, cb);
  751. }
  752. }
  753.  
  754. var entry = list[idx++];
  755.  
  756. // get the path to the file
  757. var subpath = null;
  758. if (entry.name[0] === '/') {
  759. // this will be the case when you call deleteRecursively() and pass
  760. // the path to a plain file
  761. subpath = entry.name;
  762. } else {
  763. if (path[path.length - 1] == '/') {
  764. subpath = path + entry.name;
  765. } else {
  766. subpath = path + '/' + entry.name
  767. }
  768. }
  769.  
  770. // delete the entry (recursively) according to its type
  771. if (entry.type === 'd') {
  772. if (entry.name === "." || entry.name === "..") {
  773. return deleteNextEntry();
  774. }
  775. self.rmdir(subpath, true, deleteNextEntry);
  776. } else {
  777. self.delete(subpath, deleteNextEntry);
  778. }
  779. }
  780. deleteNextEntry();
  781. });
  782. };
  783.  
  784. FTP.prototype.system = function(cb) { // SYST is optional
  785. this._send('SYST', function(err, text) {
  786. if (err)
  787. return cb(err);
  788. cb(undefined, RE_SYST.exec(text)[1]);
  789. });
  790. };
  791.  
  792. // "Extended" (RFC 3659) commands
  793. FTP.prototype.size = function(path, cb) {
  794. var self = this;
  795. this._send('SIZE ' + path, function(err, text, code) {
  796. if (code === 502) {
  797. // Note: this may cause a problem as list() is _appended_ to the queue
  798. return self.list(path, function(err, list) {
  799. if (err)
  800. return cb(err);
  801. if (list.length === 1)
  802. cb(undefined, list[0].size);
  803. else {
  804. // path could have been a directory and we got a listing of its
  805. // contents, but here we echo the behavior of the real SIZE and
  806. // return 'File not found' for directories
  807. cb(new Error('File not found'));
  808. }
  809. }, true);
  810. } else if (err)
  811. return cb(err);
  812. cb(undefined, parseInt(text, 10));
  813. });
  814. };
  815.  
  816. FTP.prototype.lastMod = function(path, cb) {
  817. var self = this;
  818. this._send('MDTM ' + path, function(err, text, code) {
  819. if (code === 502) {
  820. return self.list(path, function(err, list) {
  821. if (err)
  822. return cb(err);
  823. if (list.length === 1)
  824. cb(undefined, list[0].date);
  825. else
  826. cb(new Error('File not found'));
  827. }, true);
  828. } else if (err)
  829. return cb(err);
  830. var val = XRegExp.exec(text, REX_TIMEVAL), ret;
  831. if (!val)
  832. return cb(new Error('Invalid date/time format from server'));
  833. ret = new Date(val.year + '-' + val.month + '-' + val.date + 'T' + val.hour
  834. + ':' + val.minute + ':' + val.second);
  835. cb(undefined, ret);
  836. });
  837. };
  838.  
  839. FTP.prototype.restart = function(offset, cb) {
  840. this._send('REST ' + offset, cb);
  841. };
  842.  
  843.  
  844.  
  845. // Private/Internal methods
  846. FTP.prototype._pasv = function(cb) {
  847. var self = this, first = true, ip, port;
  848. this._send('PASV', function reentry(err, text) {
  849. if (err)
  850. return cb(err);
  851.  
  852. self._curReq = undefined;
  853.  
  854. if (first) {
  855. var m = RE_PASV.exec(text);
  856. if (!m)
  857. return cb(new Error('Unable to parse PASV server response'));
  858. ip = m[1];
  859. ip += '.';
  860. ip += m[2];
  861. ip += '.';
  862. ip += m[3];
  863. ip += '.';
  864. ip += m[4];
  865. port = (parseInt(m[5], 10) * 256) + parseInt(m[6], 10);
  866.  
  867. first = false;
  868. }
  869. self._pasvConnect(ip, port, function(err, sock) {
  870. if (err) {
  871. // try the IP of the control connection if the server was somehow
  872. // misconfigured and gave for example a LAN IP instead of WAN IP over
  873. // the Internet
  874. if (self._socket && ip !== self._socket.remoteAddress) {
  875. ip = self._socket.remoteAddress;
  876. return reentry();
  877. }
  878.  
  879. // automatically abort PASV mode
  880. self._send('ABOR', function() {
  881. cb(err);
  882. self._send();
  883. }, true);
  884.  
  885. return;
  886. }
  887. cb(undefined, sock);
  888. self._send();
  889. });
  890. });
  891. };
  892.  
  893. FTP.prototype._pasvConnect = function(ip, port, cb) {
  894. var self = this,
  895. socket = new Socket(),
  896. sockerr,
  897. timedOut = false,
  898. timer = setTimeout(function() {
  899. timedOut = true;
  900. socket.destroy();
  901. cb(new Error('Timed out while making data connection'));
  902. }, this.options.pasvTimeout);
  903.  
  904. socket.setTimeout(0);
  905.  
  906. socket.once('connect', function() {
  907. self._debug&&self._debug('[connection] PASV socket connected');
  908. if (self.options.secure === true) {
  909. self.options.secureOptions.socket = socket;
  910. self.options.secureOptions.session = self._socket.getSession();
  911. //socket.removeAllListeners('error');
  912. socket = tls.connect(self.options.secureOptions);
  913. //socket.once('error', onerror);
  914. socket.setTimeout(0);
  915. }
  916. clearTimeout(timer);
  917. self._pasvSocket = socket;
  918. cb(undefined, socket);
  919. });
  920. socket.once('error', onerror);
  921. function onerror(err) {
  922. sockerr = err;
  923. }
  924. socket.once('end', function() {
  925. clearTimeout(timer);
  926. });
  927. socket.once('close', function(had_err) {
  928. clearTimeout(timer);
  929. if (!self._pasvSocket && !timedOut) {
  930. var errmsg = 'Unable to make data connection';
  931. if (sockerr) {
  932. errmsg += '( ' + sockerr + ')';
  933. sockerr = undefined;
  934. }
  935. cb(new Error(errmsg));
  936. }
  937. self._pasvSocket = undefined;
  938. });
  939.  
  940. socket.connect(port, ip);
  941. };
  942.  
  943. FTP.prototype._store = function(cmd, input, zcomp, cb) {
  944. var isBuffer = Buffer.isBuffer(input);
  945.  
  946. if (!isBuffer && input.pause !== undefined)
  947. input.pause();
  948.  
  949. if (typeof zcomp === 'function') {
  950. cb = zcomp;
  951. zcomp = false;
  952. }
  953.  
  954. var self = this;
  955. this._pasv(function(err, sock) {
  956. if (err)
  957. return cb(err);
  958.  
  959. if (self._queue[0] && self._queue[0].cmd === 'ABOR') {
  960. sock.destroy();
  961. return cb();
  962. }
  963.  
  964. var sockerr, dest = sock;
  965. sock.once('error', function(err) {
  966. sockerr = err;
  967. });
  968.  
  969. if (zcomp) {
  970. self._send('MODE Z', function(err, text, code) {
  971. if (err) {
  972. sock.destroy();
  973. return cb(makeError(code, 'Compression not supported'));
  974. }
  975. // draft-preston-ftpext-deflate-04 says min of 8 should be supported
  976. dest = zlib.createDeflate({ level: 8 });
  977. dest.pipe(sock);
  978. sendStore();
  979. }, true);
  980. } else
  981. sendStore();
  982.  
  983. function sendStore() {
  984. // this callback will be executed multiple times, the first is when server
  985. // replies with 150, then a final reply after the data connection closes
  986. // to indicate whether the transfer was actually a success or not
  987. self._send(cmd, function(err, text, code) {
  988. if (sockerr || err) {
  989. if (zcomp) {
  990. self._send('MODE S', function() {
  991. cb(sockerr || err);
  992. }, true);
  993. } else
  994. cb(sockerr || err);
  995. return;
  996. }
  997.  
  998. if (code === 150 || code === 125) {
  999. if (isBuffer)
  1000. dest.end(input);
  1001. else if (typeof input === 'string') {
  1002. // check if input is a file path or just string data to store
  1003. fs.stat(input, function(err, stats) {
  1004. if (err)
  1005. dest.end(input);
  1006. else
  1007. fs.createReadStream(input).pipe(dest);
  1008. });
  1009. } else {
  1010. input.pipe(dest);
  1011. input.resume();
  1012. }
  1013. } else {
  1014. if (zcomp)
  1015. self._send('MODE S', cb, true);
  1016. else
  1017. cb();
  1018. }
  1019. }, true);
  1020. }
  1021. });
  1022. };
  1023.  
  1024. FTP.prototype._send = function(cmd, cb, promote) {
  1025. clearTimeout(this._keepalive);
  1026. if (cmd !== undefined) {
  1027. if (promote)
  1028. this._queue.unshift({ cmd: cmd, cb: cb });
  1029. else
  1030. this._queue.push({ cmd: cmd, cb: cb });
  1031. }
  1032. var queueLen = this._queue.length;
  1033. if (!this._curReq && queueLen && this._socket && this._socket.readable) {
  1034. this._curReq = this._queue.shift();
  1035. if (this._curReq.cmd === 'ABOR' && this._pasvSocket)
  1036. this._pasvSocket.aborting = true;
  1037. this._debug&&this._debug('[connection] > ' + inspect(this._curReq.cmd));
  1038. this._socket.write(this._curReq.cmd + '\r\n');
  1039. } else if (!this._curReq && !queueLen && this._ending)
  1040. this._reset();
  1041. };
  1042.  
  1043. FTP.prototype._reset = function() {
  1044. if (this._pasvSock && this._pasvSock.writable)
  1045. this._pasvSock.end();
  1046. if (this._socket && this._socket.writable)
  1047. this._socket.end();
  1048. this._socket = undefined;
  1049. this._pasvSock = undefined;
  1050. this._feat = undefined;
  1051. this._curReq = undefined;
  1052. this._secstate = undefined;
  1053. clearTimeout(this._keepalive);
  1054. this._keepalive = undefined;
  1055. this._queue = [];
  1056. this._ending = false;
  1057. this._parser = undefined;
  1058. this.options.host = this.options.port = this.options.user
  1059. = this.options.password = this.options.secure
  1060. = this.options.connTimeout = this.options.pasvTimeout
  1061. = this.options.keepalive = this._debug = undefined;
  1062. this.connected = false;
  1063. };
  1064.  
  1065. // Utility functions
  1066. function makeError(code, text) {
  1067. var err = new Error(text);
  1068. err.code = code;
  1069. return err;
  1070. }
Add Comment
Please, Sign In to add comment