You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					1071 lines
				
				29 KiB
			
		
		
			
		
	
	
					1071 lines
				
				29 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								var fs = require('fs'),
							 | 
						||
| 
								 | 
							
								    tls = require('tls'),
							 | 
						||
| 
								 | 
							
								    zlib = require('zlib'),
							 | 
						||
| 
								 | 
							
								    Socket = require('net').Socket,
							 | 
						||
| 
								 | 
							
								    EventEmitter = require('events').EventEmitter,
							 | 
						||
| 
								 | 
							
								    inherits = require('util').inherits,
							 | 
						||
| 
								 | 
							
								    inspect = require('util').inspect;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var Parser = require('./parser');
							 | 
						||
| 
								 | 
							
								var XRegExp = require('xregexp').XRegExp;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var REX_TIMEVAL = XRegExp.cache('^(?<year>\\d{4})(?<month>\\d{2})(?<date>\\d{2})(?<hour>\\d{2})(?<minute>\\d{2})(?<second>\\d+)(?:.\\d+)?$'),
							 | 
						||
| 
								 | 
							
								    RE_PASV = /([\d]+),([\d]+),([\d]+),([\d]+),([-\d]+),([-\d]+)/,
							 | 
						||
| 
								 | 
							
								    RE_EOL = /\r?\n/g,
							 | 
						||
| 
								 | 
							
								    RE_WD = /"(.+)"(?: |$)/,
							 | 
						||
| 
								 | 
							
								    RE_SYST = /^([^ ]+)(?: |$)/;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var /*TYPE = {
							 | 
						||
| 
								 | 
							
								      SYNTAX: 0,
							 | 
						||
| 
								 | 
							
								      INFO: 1,
							 | 
						||
| 
								 | 
							
								      SOCKETS: 2,
							 | 
						||
| 
								 | 
							
								      AUTH: 3,
							 | 
						||
| 
								 | 
							
								      UNSPEC: 4,
							 | 
						||
| 
								 | 
							
								      FILESYS: 5
							 | 
						||
| 
								 | 
							
								    },*/
							 | 
						||
| 
								 | 
							
								    RETVAL = {
							 | 
						||
| 
								 | 
							
								      PRELIM: 1,
							 | 
						||
| 
								 | 
							
								      OK: 2,
							 | 
						||
| 
								 | 
							
								      WAITING: 3,
							 | 
						||
| 
								 | 
							
								      ERR_TEMP: 4,
							 | 
						||
| 
								 | 
							
								      ERR_PERM: 5
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    /*ERRORS = {
							 | 
						||
| 
								 | 
							
								      421: 'Service not available, closing control connection',
							 | 
						||
| 
								 | 
							
								      425: 'Can\'t open data connection',
							 | 
						||
| 
								 | 
							
								      426: 'Connection closed; transfer aborted',
							 | 
						||
| 
								 | 
							
								      450: 'Requested file action not taken / File unavailable (e.g., file busy)',
							 | 
						||
| 
								 | 
							
								      451: 'Requested action aborted: local error in processing',
							 | 
						||
| 
								 | 
							
								      452: 'Requested action not taken / Insufficient storage space in system',
							 | 
						||
| 
								 | 
							
								      500: 'Syntax error / Command unrecognized',
							 | 
						||
| 
								 | 
							
								      501: 'Syntax error in parameters or arguments',
							 | 
						||
| 
								 | 
							
								      502: 'Command not implemented',
							 | 
						||
| 
								 | 
							
								      503: 'Bad sequence of commands',
							 | 
						||
| 
								 | 
							
								      504: 'Command not implemented for that parameter',
							 | 
						||
| 
								 | 
							
								      530: 'Not logged in',
							 | 
						||
| 
								 | 
							
								      532: 'Need account for storing files',
							 | 
						||
| 
								 | 
							
								      550: 'Requested action not taken / File unavailable (e.g., file not found, no access)',
							 | 
						||
| 
								 | 
							
								      551: 'Requested action aborted: page type unknown',
							 | 
						||
| 
								 | 
							
								      552: 'Requested file action aborted / Exceeded storage allocation (for current directory or dataset)',
							 | 
						||
| 
								 | 
							
								      553: 'Requested action not taken / File name not allowed'
							 | 
						||
| 
								 | 
							
								    },*/
							 | 
						||
| 
								 | 
							
								    bytesNOOP = new Buffer('NOOP\r\n');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var FTP = module.exports = function() {
							 | 
						||
| 
								 | 
							
								  if (!(this instanceof FTP))
							 | 
						||
| 
								 | 
							
								    return new FTP();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._socket = undefined;
							 | 
						||
| 
								 | 
							
								  this._pasvSock = undefined;
							 | 
						||
| 
								 | 
							
								  this._feat = undefined;
							 | 
						||
| 
								 | 
							
								  this._curReq = undefined;
							 | 
						||
| 
								 | 
							
								  this._queue = [];
							 | 
						||
| 
								 | 
							
								  this._secstate = undefined;
							 | 
						||
| 
								 | 
							
								  this._debug = undefined;
							 | 
						||
| 
								 | 
							
								  this._keepalive = undefined;
							 | 
						||
| 
								 | 
							
								  this._ending = false;
							 | 
						||
| 
								 | 
							
								  this._parser = undefined;
							 | 
						||
| 
								 | 
							
								  this.options = {
							 | 
						||
| 
								 | 
							
								    host: undefined,
							 | 
						||
| 
								 | 
							
								    port: undefined,
							 | 
						||
| 
								 | 
							
								    user: undefined,
							 | 
						||
| 
								 | 
							
								    password: undefined,
							 | 
						||
| 
								 | 
							
								    secure: false,
							 | 
						||
| 
								 | 
							
								    secureOptions: undefined,
							 | 
						||
| 
								 | 
							
								    connTimeout: undefined,
							 | 
						||
| 
								 | 
							
								    pasvTimeout: undefined,
							 | 
						||
| 
								 | 
							
								    aliveTimeout: undefined
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								  this.connected = false;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								inherits(FTP, EventEmitter);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.connect = function(options) {
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  if (typeof options !== 'object')
							 | 
						||
| 
								 | 
							
								    options = {};
							 | 
						||
| 
								 | 
							
								  this.connected = false;
							 | 
						||
| 
								 | 
							
								  this.options.host = options.host || 'localhost';
							 | 
						||
| 
								 | 
							
								  this.options.port = options.port || 21;
							 | 
						||
| 
								 | 
							
								  this.options.user = options.user || 'anonymous';
							 | 
						||
| 
								 | 
							
								  this.options.password = options.password || 'anonymous@';
							 | 
						||
| 
								 | 
							
								  this.options.secure = options.secure || false;
							 | 
						||
| 
								 | 
							
								  this.options.secureOptions = options.secureOptions;
							 | 
						||
| 
								 | 
							
								  this.options.connTimeout = options.connTimeout || 10000;
							 | 
						||
| 
								 | 
							
								  this.options.pasvTimeout = options.pasvTimeout || 10000;
							 | 
						||
| 
								 | 
							
								  this.options.aliveTimeout = options.keepalive || 10000;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof options.debug === 'function')
							 | 
						||
| 
								 | 
							
								    this._debug = options.debug;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var secureOptions,
							 | 
						||
| 
								 | 
							
								      debug = this._debug,
							 | 
						||
| 
								 | 
							
								      socket = new Socket();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  socket.setTimeout(0);
							 | 
						||
| 
								 | 
							
								  socket.setKeepAlive(true);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._parser = new Parser({ debug: debug });
							 | 
						||
| 
								 | 
							
								  this._parser.on('response', function(code, text) {
							 | 
						||
| 
								 | 
							
								    var retval = code / 100 >> 0;
							 | 
						||
| 
								 | 
							
								    if (retval === RETVAL.ERR_TEMP || retval === RETVAL.ERR_PERM) {
							 | 
						||
| 
								 | 
							
								      if (self._curReq)
							 | 
						||
| 
								 | 
							
								        self._curReq.cb(makeError(code, text), undefined, code);
							 | 
						||
| 
								 | 
							
								      else
							 | 
						||
| 
								 | 
							
								        self.emit('error', makeError(code, text));
							 | 
						||
| 
								 | 
							
								    } else if (self._curReq)
							 | 
						||
| 
								 | 
							
								      self._curReq.cb(undefined, text, code);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // a hack to signal we're waiting for a PASV data connection to complete
							 | 
						||
| 
								 | 
							
								    // first before executing any more queued requests ...
							 | 
						||
| 
								 | 
							
								    //
							 | 
						||
| 
								 | 
							
								    // also: don't forget our current request if we're expecting another
							 | 
						||
| 
								 | 
							
								    // terminating response ....
							 | 
						||
| 
								 | 
							
								    if (self._curReq && retval !== RETVAL.PRELIM) {
							 | 
						||
| 
								 | 
							
								      self._curReq = undefined;
							 | 
						||
| 
								 | 
							
								      self._send();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    noopreq.cb();
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this.options.secure) {
							 | 
						||
| 
								 | 
							
								    secureOptions = {};
							 | 
						||
| 
								 | 
							
								    secureOptions.host = this.options.host;
							 | 
						||
| 
								 | 
							
								    for (var k in this.options.secureOptions)
							 | 
						||
| 
								 | 
							
								      secureOptions[k] = this.options.secureOptions[k];
							 | 
						||
| 
								 | 
							
								    secureOptions.socket = socket;
							 | 
						||
| 
								 | 
							
								    this.options.secureOptions = secureOptions;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this.options.secure === 'implicit')
							 | 
						||
| 
								 | 
							
								    this._socket = tls.connect(secureOptions, onconnect);
							 | 
						||
| 
								 | 
							
								  else {
							 | 
						||
| 
								 | 
							
								    socket.once('connect', onconnect);
							 | 
						||
| 
								 | 
							
								    this._socket = socket;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var noopreq = {
							 | 
						||
| 
								 | 
							
								        cmd: 'NOOP',
							 | 
						||
| 
								 | 
							
								        cb: function() {
							 | 
						||
| 
								 | 
							
								          clearTimeout(self._keepalive);
							 | 
						||
| 
								 | 
							
								          self._keepalive = setTimeout(donoop, self.options.aliveTimeout);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function donoop() {
							 | 
						||
| 
								 | 
							
								    if (!self._socket || !self._socket.writable)
							 | 
						||
| 
								 | 
							
								      clearTimeout(self._keepalive);
							 | 
						||
| 
								 | 
							
								    else if (!self._curReq && self._queue.length === 0) {
							 | 
						||
| 
								 | 
							
								      self._curReq = noopreq;
							 | 
						||
| 
								 | 
							
								      debug&&debug('[connection] > NOOP');
							 | 
						||
| 
								 | 
							
								      self._socket.write(bytesNOOP);
							 | 
						||
| 
								 | 
							
								    } else
							 | 
						||
| 
								 | 
							
								      noopreq.cb();
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function onconnect() {
							 | 
						||
| 
								 | 
							
								    clearTimeout(timer);
							 | 
						||
| 
								 | 
							
								    clearTimeout(self._keepalive);
							 | 
						||
| 
								 | 
							
								    self.connected = true;
							 | 
						||
| 
								 | 
							
								    self._socket = socket; // re-assign for implicit secure connections
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var cmd;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (self._secstate) {
							 | 
						||
| 
								 | 
							
								      if (self._secstate === 'upgraded-tls' && self.options.secure === true) {
							 | 
						||
| 
								 | 
							
								        cmd = 'PBSZ';
							 | 
						||
| 
								 | 
							
								        self._send('PBSZ 0', reentry, true);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        cmd = 'USER';
							 | 
						||
| 
								 | 
							
								        self._send('USER ' + self.options.user, reentry, true);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      self._curReq = {
							 | 
						||
| 
								 | 
							
								        cmd: '',
							 | 
						||
| 
								 | 
							
								        cb: reentry
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function reentry(err, text, code) {
							 | 
						||
| 
								 | 
							
								      if (err && (!cmd || cmd === 'USER' || cmd === 'PASS' || cmd === 'TYPE')) {
							 | 
						||
| 
								 | 
							
								        self.emit('error', err);
							 | 
						||
| 
								 | 
							
								        return self._socket && self._socket.end();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if ((cmd === 'AUTH TLS' && code !== 234 && self.options.secure !== true)
							 | 
						||
| 
								 | 
							
								          || (cmd === 'AUTH SSL' && code !== 334)
							 | 
						||
| 
								 | 
							
								          || (cmd === 'PBSZ' && code !== 200)
							 | 
						||
| 
								 | 
							
								          || (cmd === 'PROT' && code !== 200)) {
							 | 
						||
| 
								 | 
							
								        self.emit('error', makeError(code, 'Unable to secure connection(s)'));
							 | 
						||
| 
								 | 
							
								        return self._socket && self._socket.end();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!cmd) {
							 | 
						||
| 
								 | 
							
								        // sometimes the initial greeting can contain useful information
							 | 
						||
| 
								 | 
							
								        // about authorized use, other limits, etc.
							 | 
						||
| 
								 | 
							
								        self.emit('greeting', text);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (self.options.secure && self.options.secure !== 'implicit') {
							 | 
						||
| 
								 | 
							
								          cmd = 'AUTH TLS';
							 | 
						||
| 
								 | 
							
								          self._send(cmd, reentry, true);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          cmd = 'USER';
							 | 
						||
| 
								 | 
							
								          self._send('USER ' + self.options.user, reentry, true);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else if (cmd === 'USER') {
							 | 
						||
| 
								 | 
							
								        if (code !== 230) {
							 | 
						||
| 
								 | 
							
								          // password required
							 | 
						||
| 
								 | 
							
								          if (!self.options.password) {
							 | 
						||
| 
								 | 
							
								            self.emit('error', makeError(code, 'Password required'));
							 | 
						||
| 
								 | 
							
								            return self._socket && self._socket.end();
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          cmd = 'PASS';
							 | 
						||
| 
								 | 
							
								          self._send('PASS ' + self.options.password, reentry, true);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          // no password required
							 | 
						||
| 
								 | 
							
								          cmd = 'PASS';
							 | 
						||
| 
								 | 
							
								          reentry(undefined, text, code);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else if (cmd === 'PASS') {
							 | 
						||
| 
								 | 
							
								        cmd = 'FEAT';
							 | 
						||
| 
								 | 
							
								        self._send(cmd, reentry, true);
							 | 
						||
| 
								 | 
							
								      } else if (cmd === 'FEAT') {
							 | 
						||
| 
								 | 
							
								        if (!err)
							 | 
						||
| 
								 | 
							
								          self._feat = Parser.parseFeat(text);
							 | 
						||
| 
								 | 
							
								        cmd = 'TYPE';
							 | 
						||
| 
								 | 
							
								        self._send('TYPE I', reentry, true);
							 | 
						||
| 
								 | 
							
								      } else if (cmd === 'TYPE')
							 | 
						||
| 
								 | 
							
								        self.emit('ready');
							 | 
						||
| 
								 | 
							
								      else if (cmd === 'PBSZ') {
							 | 
						||
| 
								 | 
							
								        cmd = 'PROT';
							 | 
						||
| 
								 | 
							
								        self._send('PROT P', reentry, true);
							 | 
						||
| 
								 | 
							
								      } else if (cmd === 'PROT') {
							 | 
						||
| 
								 | 
							
								        cmd = 'USER';
							 | 
						||
| 
								 | 
							
								        self._send('USER ' + self.options.user, reentry, true);
							 | 
						||
| 
								 | 
							
								      } else if (cmd.substr(0, 4) === 'AUTH') {
							 | 
						||
| 
								 | 
							
								        if (cmd === 'AUTH TLS' && code !== 234) {
							 | 
						||
| 
								 | 
							
								          cmd = 'AUTH SSL';
							 | 
						||
| 
								 | 
							
								          return self._send(cmd, reentry, true);
							 | 
						||
| 
								 | 
							
								        } else if (cmd === 'AUTH TLS')
							 | 
						||
| 
								 | 
							
								          self._secstate = 'upgraded-tls';
							 | 
						||
| 
								 | 
							
								        else if (cmd === 'AUTH SSL')
							 | 
						||
| 
								 | 
							
								          self._secstate = 'upgraded-ssl';
							 | 
						||
| 
								 | 
							
								        socket.removeAllListeners('data');
							 | 
						||
| 
								 | 
							
								        socket.removeAllListeners('error');
							 | 
						||
| 
								 | 
							
								        socket._decoder = null;
							 | 
						||
| 
								 | 
							
								        self._curReq = null; // prevent queue from being processed during
							 | 
						||
| 
								 | 
							
								                             // TLS/SSL negotiation
							 | 
						||
| 
								 | 
							
								        secureOptions.socket = self._socket;
							 | 
						||
| 
								 | 
							
								        secureOptions.session = undefined;
							 | 
						||
| 
								 | 
							
								        socket = tls.connect(secureOptions, onconnect);
							 | 
						||
| 
								 | 
							
								        socket.setEncoding('binary');
							 | 
						||
| 
								 | 
							
								        socket.on('data', ondata);
							 | 
						||
| 
								 | 
							
								        socket.once('end', onend);
							 | 
						||
| 
								 | 
							
								        socket.on('error', onerror);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  socket.on('data', ondata);
							 | 
						||
| 
								 | 
							
								  function ondata(chunk) {
							 | 
						||
| 
								 | 
							
								    debug&&debug('[connection] < ' + inspect(chunk.toString('binary')));
							 | 
						||
| 
								 | 
							
								    if (self._parser)
							 | 
						||
| 
								 | 
							
								      self._parser.write(chunk);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  socket.on('error', onerror);
							 | 
						||
| 
								 | 
							
								  function onerror(err) {
							 | 
						||
| 
								 | 
							
								    clearTimeout(timer);
							 | 
						||
| 
								 | 
							
								    clearTimeout(self._keepalive);
							 | 
						||
| 
								 | 
							
								    self.emit('error', err);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  socket.once('end', onend);
							 | 
						||
| 
								 | 
							
								  function onend() {
							 | 
						||
| 
								 | 
							
								    ondone();
							 | 
						||
| 
								 | 
							
								    self.emit('end');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  socket.once('close', function(had_err) {
							 | 
						||
| 
								 | 
							
								    ondone();
							 | 
						||
| 
								 | 
							
								    self.emit('close', had_err);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var hasReset = false;
							 | 
						||
| 
								 | 
							
								  function ondone() {
							 | 
						||
| 
								 | 
							
								    if (!hasReset) {
							 | 
						||
| 
								 | 
							
								      hasReset = true;
							 | 
						||
| 
								 | 
							
								      clearTimeout(timer);
							 | 
						||
| 
								 | 
							
								      self._reset();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var timer = setTimeout(function() {
							 | 
						||
| 
								 | 
							
								    self.emit('error', new Error('Timeout while connecting to server'));
							 | 
						||
| 
								 | 
							
								    self._socket && self._socket.destroy();
							 | 
						||
| 
								 | 
							
								    self._reset();
							 | 
						||
| 
								 | 
							
								  }, this.options.connTimeout);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._socket.connect(this.options.port, this.options.host);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.end = function() {
							 | 
						||
| 
								 | 
							
								  if (this._queue.length)
							 | 
						||
| 
								 | 
							
								    this._ending = true;
							 | 
						||
| 
								 | 
							
								  else
							 | 
						||
| 
								 | 
							
								    this._reset();
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.destroy = function() {
							 | 
						||
| 
								 | 
							
								  this._reset();
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// "Standard" (RFC 959) commands
							 | 
						||
| 
								 | 
							
								FTP.prototype.ascii = function(cb) {
							 | 
						||
| 
								 | 
							
								  return this._send('TYPE A', cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.binary = function(cb) {
							 | 
						||
| 
								 | 
							
								  return this._send('TYPE I', cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.abort = function(immediate, cb) {
							 | 
						||
| 
								 | 
							
								  if (typeof immediate === 'function') {
							 | 
						||
| 
								 | 
							
								    cb = immediate;
							 | 
						||
| 
								 | 
							
								    immediate = true;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (immediate)
							 | 
						||
| 
								 | 
							
								    this._send('ABOR', cb, true);
							 | 
						||
| 
								 | 
							
								  else
							 | 
						||
| 
								 | 
							
								    this._send('ABOR', cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.cwd = function(path, cb, promote) {
							 | 
						||
| 
								 | 
							
								  this._send('CWD ' + path, function(err, text, code) {
							 | 
						||
| 
								 | 
							
								    if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								    var m = RE_WD.exec(text);
							 | 
						||
| 
								 | 
							
								    cb(undefined, m ? m[1] : undefined);
							 | 
						||
| 
								 | 
							
								  }, promote);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.delete = function(path, cb) {
							 | 
						||
| 
								 | 
							
								  this._send('DELE ' + path, cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.site = function(cmd, cb) {
							 | 
						||
| 
								 | 
							
								  this._send('SITE ' + cmd, cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.status = function(cb) {
							 | 
						||
| 
								 | 
							
								  this._send('STAT', cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.rename = function(from, to, cb) {
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  this._send('RNFR ' + from, function(err) {
							 | 
						||
| 
								 | 
							
								    if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    self._send('RNTO ' + to, cb, true);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.logout = function(cb) {
							 | 
						||
| 
								 | 
							
								  this._send('QUIT', cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.listSafe = function(path, zcomp, cb) {
							 | 
						||
| 
								 | 
							
								  if (typeof path === 'string') {
							 | 
						||
| 
								 | 
							
								    var self = this;
							 | 
						||
| 
								 | 
							
								    // store current path
							 | 
						||
| 
								 | 
							
								    this.pwd(function(err, origpath) {
							 | 
						||
| 
								 | 
							
								      if (err) return cb(err);
							 | 
						||
| 
								 | 
							
								      // change to destination path
							 | 
						||
| 
								 | 
							
								      self.cwd(path, function(err) {
							 | 
						||
| 
								 | 
							
								        if (err) return cb(err);
							 | 
						||
| 
								 | 
							
								        // get dir listing
							 | 
						||
| 
								 | 
							
								        self.list(zcomp || false, function(err, list) {
							 | 
						||
| 
								 | 
							
								          // change back to original path
							 | 
						||
| 
								 | 
							
								          if (err) return self.cwd(origpath, cb);
							 | 
						||
| 
								 | 
							
								          self.cwd(origpath, function(err) {
							 | 
						||
| 
								 | 
							
								            if (err) return cb(err);
							 | 
						||
| 
								 | 
							
								            cb(err, list);
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  } else
							 | 
						||
| 
								 | 
							
								    this.list(path, zcomp, cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.list = function(path, zcomp, cb) {
							 | 
						||
| 
								 | 
							
								  var self = this, cmd;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof path === 'function') {
							 | 
						||
| 
								 | 
							
								    // list(function() {})
							 | 
						||
| 
								 | 
							
								    cb = path;
							 | 
						||
| 
								 | 
							
								    path = undefined;
							 | 
						||
| 
								 | 
							
								    cmd = 'LIST';
							 | 
						||
| 
								 | 
							
								    zcomp = false;
							 | 
						||
| 
								 | 
							
								  } else if (typeof path === 'boolean') {
							 | 
						||
| 
								 | 
							
								    // list(true, function() {})
							 | 
						||
| 
								 | 
							
								    cb = zcomp;
							 | 
						||
| 
								 | 
							
								    zcomp = path;
							 | 
						||
| 
								 | 
							
								    path = undefined;
							 | 
						||
| 
								 | 
							
								    cmd = 'LIST';
							 | 
						||
| 
								 | 
							
								  } else if (typeof zcomp === 'function') {
							 | 
						||
| 
								 | 
							
								    // list('/foo', function() {})
							 | 
						||
| 
								 | 
							
								    cb = zcomp;
							 | 
						||
| 
								 | 
							
								    cmd = 'LIST ' + path;
							 | 
						||
| 
								 | 
							
								    zcomp = false;
							 | 
						||
| 
								 | 
							
								  } else
							 | 
						||
| 
								 | 
							
								    cmd = 'LIST ' + path;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._pasv(function(err, sock) {
							 | 
						||
| 
								 | 
							
								    if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (self._queue[0] && self._queue[0].cmd === 'ABOR') {
							 | 
						||
| 
								 | 
							
								      sock.destroy();
							 | 
						||
| 
								 | 
							
								      return cb();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var sockerr, done = false, replies = 0, entries, buffer = '', source = sock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (zcomp) {
							 | 
						||
| 
								 | 
							
								      source = zlib.createInflate();
							 | 
						||
| 
								 | 
							
								      sock.pipe(source);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    source.on('data', function(chunk) { buffer += chunk.toString('binary'); });
							 | 
						||
| 
								 | 
							
								    source.once('error', function(err) {
							 | 
						||
| 
								 | 
							
								      if (!sock.aborting)
							 | 
						||
| 
								 | 
							
								        sockerr = err;
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    source.once('end', ondone);
							 | 
						||
| 
								 | 
							
								    source.once('close', ondone);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function ondone() {
							 | 
						||
| 
								 | 
							
								      done = true;
							 | 
						||
| 
								 | 
							
								      final();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    function final() {
							 | 
						||
| 
								 | 
							
								      if (done && replies === 2) {
							 | 
						||
| 
								 | 
							
								        replies = 3;
							 | 
						||
| 
								 | 
							
								        if (sockerr)
							 | 
						||
| 
								 | 
							
								          return cb(new Error('Unexpected data connection error: ' + sockerr));
							 | 
						||
| 
								 | 
							
								        if (sock.aborting)
							 | 
						||
| 
								 | 
							
								          return cb();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // process received data
							 | 
						||
| 
								 | 
							
								        entries = buffer.split(RE_EOL);
							 | 
						||
| 
								 | 
							
								        entries.pop(); // ending EOL
							 | 
						||
| 
								 | 
							
								        var parsed = [];
							 | 
						||
| 
								 | 
							
								        for (var i = 0, len = entries.length; i < len; ++i) {
							 | 
						||
| 
								 | 
							
								          var parsedVal = Parser.parseListEntry(entries[i]);
							 | 
						||
| 
								 | 
							
								          if (parsedVal !== null)
							 | 
						||
| 
								 | 
							
								            parsed.push(parsedVal);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (zcomp) {
							 | 
						||
| 
								 | 
							
								          self._send('MODE S', function() {
							 | 
						||
| 
								 | 
							
								            cb(undefined, parsed);
							 | 
						||
| 
								 | 
							
								          }, true);
							 | 
						||
| 
								 | 
							
								        } else
							 | 
						||
| 
								 | 
							
								          cb(undefined, parsed);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (zcomp) {
							 | 
						||
| 
								 | 
							
								      self._send('MODE Z', function(err, text, code) {
							 | 
						||
| 
								 | 
							
								        if (err) {
							 | 
						||
| 
								 | 
							
								          sock.destroy();
							 | 
						||
| 
								 | 
							
								          return cb(makeError(code, 'Compression not supported'));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        sendList();
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    } else
							 | 
						||
| 
								 | 
							
								      sendList();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function sendList() {
							 | 
						||
| 
								 | 
							
								      // this callback will be executed multiple times, the first is when server
							 | 
						||
| 
								 | 
							
								      // replies with 150 and then a final reply to indicate whether the
							 | 
						||
| 
								 | 
							
								      // transfer was actually a success or not
							 | 
						||
| 
								 | 
							
								      self._send(cmd, function(err, text, code) {
							 | 
						||
| 
								 | 
							
								        if (err) {
							 | 
						||
| 
								 | 
							
								          sock.destroy();
							 | 
						||
| 
								 | 
							
								          if (zcomp) {
							 | 
						||
| 
								 | 
							
								            self._send('MODE S', function() {
							 | 
						||
| 
								 | 
							
								              cb(err);
							 | 
						||
| 
								 | 
							
								            }, true);
							 | 
						||
| 
								 | 
							
								          } else
							 | 
						||
| 
								 | 
							
								            cb(err);
							 | 
						||
| 
								 | 
							
								          return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // some servers may not open a data connection for empty directories
							 | 
						||
| 
								 | 
							
								        if (++replies === 1 && code === 226) {
							 | 
						||
| 
								 | 
							
								          replies = 2;
							 | 
						||
| 
								 | 
							
								          sock.destroy();
							 | 
						||
| 
								 | 
							
								          final();
							 | 
						||
| 
								 | 
							
								        } else if (replies === 2)
							 | 
						||
| 
								 | 
							
								          final();
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.get = function(path, zcomp, cb) {
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  if (typeof zcomp === 'function') {
							 | 
						||
| 
								 | 
							
								    cb = zcomp;
							 | 
						||
| 
								 | 
							
								    zcomp = false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._pasv(function(err, sock) {
							 | 
						||
| 
								 | 
							
								    if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (self._queue[0] && self._queue[0].cmd === 'ABOR') {
							 | 
						||
| 
								 | 
							
								      sock.destroy();
							 | 
						||
| 
								 | 
							
								      return cb();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // modify behavior of socket events so that we can emit 'error' once for
							 | 
						||
| 
								 | 
							
								    // either a TCP-level error OR an FTP-level error response that we get when
							 | 
						||
| 
								 | 
							
								    // the socket is closed (e.g. the server ran out of space).
							 | 
						||
| 
								 | 
							
								    var sockerr, started = false, lastreply = false, done = false,
							 | 
						||
| 
								 | 
							
								        source = sock;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (zcomp) {
							 | 
						||
| 
								 | 
							
								      source = zlib.createInflate();
							 | 
						||
| 
								 | 
							
								      sock.pipe(source);
							 | 
						||
| 
								 | 
							
								      sock._emit = sock.emit;
							 | 
						||
| 
								 | 
							
								      sock.emit = function(ev, arg1) {
							 | 
						||
| 
								 | 
							
								        if (ev === 'error') {
							 | 
						||
| 
								 | 
							
								          if (!sockerr)
							 | 
						||
| 
								 | 
							
								            sockerr = arg1;
							 | 
						||
| 
								 | 
							
								          return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        sock._emit.apply(sock, Array.prototype.slice.call(arguments));
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    source._emit = source.emit;
							 | 
						||
| 
								 | 
							
								    source.emit = function(ev, arg1) {
							 | 
						||
| 
								 | 
							
								      if (ev === 'error') {
							 | 
						||
| 
								 | 
							
								        if (!sockerr)
							 | 
						||
| 
								 | 
							
								          sockerr = arg1;
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      } else if (ev === 'end' || ev === 'close') {
							 | 
						||
| 
								 | 
							
								        if (!done) {
							 | 
						||
| 
								 | 
							
								          done = true;
							 | 
						||
| 
								 | 
							
								          ondone();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      source._emit.apply(source, Array.prototype.slice.call(arguments));
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function ondone() {
							 | 
						||
| 
								 | 
							
								      if (done && lastreply) {
							 | 
						||
| 
								 | 
							
								        self._send('MODE S', function() {
							 | 
						||
| 
								 | 
							
								          source._emit('end');
							 | 
						||
| 
								 | 
							
								          source._emit('close');
							 | 
						||
| 
								 | 
							
								        }, true);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    sock.pause();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (zcomp) {
							 | 
						||
| 
								 | 
							
								      self._send('MODE Z', function(err, text, code) {
							 | 
						||
| 
								 | 
							
								        if (err) {
							 | 
						||
| 
								 | 
							
								          sock.destroy();
							 | 
						||
| 
								 | 
							
								          return cb(makeError(code, 'Compression not supported'));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        sendRetr();
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    } else
							 | 
						||
| 
								 | 
							
								      sendRetr();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function sendRetr() {
							 | 
						||
| 
								 | 
							
								      // this callback will be executed multiple times, the first is when server
							 | 
						||
| 
								 | 
							
								      // replies with 150, then a final reply after the data connection closes
							 | 
						||
| 
								 | 
							
								      // to indicate whether the transfer was actually a success or not
							 | 
						||
| 
								 | 
							
								      self._send('RETR ' + path, function(err, text, code) {
							 | 
						||
| 
								 | 
							
								        if (sockerr || err) {
							 | 
						||
| 
								 | 
							
								          sock.destroy();
							 | 
						||
| 
								 | 
							
								          if (!started) {
							 | 
						||
| 
								 | 
							
								            if (zcomp) {
							 | 
						||
| 
								 | 
							
								              self._send('MODE S', function() {
							 | 
						||
| 
								 | 
							
								                cb(sockerr || err);
							 | 
						||
| 
								 | 
							
								              }, true);
							 | 
						||
| 
								 | 
							
								            } else
							 | 
						||
| 
								 | 
							
								              cb(sockerr || err);
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            source._emit('error', sockerr || err);
							 | 
						||
| 
								 | 
							
								            source._emit('close', true);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        // server returns 125 when data connection is already open; we treat it
							 | 
						||
| 
								 | 
							
								        // just like a 150
							 | 
						||
| 
								 | 
							
								        if (code === 150 || code === 125) {
							 | 
						||
| 
								 | 
							
								          started = true;
							 | 
						||
| 
								 | 
							
								          cb(undefined, source);
							 | 
						||
| 
								 | 
							
								          sock.resume();
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          lastreply = true;
							 | 
						||
| 
								 | 
							
								          ondone();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.put = function(input, path, zcomp, cb) {
							 | 
						||
| 
								 | 
							
								  this._store('STOR ' + path, input, zcomp, cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.append = function(input, path, zcomp, cb) {
							 | 
						||
| 
								 | 
							
								  this._store('APPE ' + path, input, zcomp, cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.pwd = function(cb) { // PWD is optional
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  this._send('PWD', function(err, text, code) {
							 | 
						||
| 
								 | 
							
								    if (code === 502) {
							 | 
						||
| 
								 | 
							
								      return self.cwd('.', function(cwderr, cwd) {
							 | 
						||
| 
								 | 
							
								        if (cwderr)
							 | 
						||
| 
								 | 
							
								          return cb(cwderr);
							 | 
						||
| 
								 | 
							
								        if (cwd === undefined)
							 | 
						||
| 
								 | 
							
								          cb(err);
							 | 
						||
| 
								 | 
							
								        else
							 | 
						||
| 
								 | 
							
								          cb(undefined, cwd);
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    } else if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								    cb(undefined, RE_WD.exec(text)[1]);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.cdup = function(cb) { // CDUP is optional
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  this._send('CDUP', function(err, text, code) {
							 | 
						||
| 
								 | 
							
								    if (code === 502)
							 | 
						||
| 
								 | 
							
								      self.cwd('..', cb, true);
							 | 
						||
| 
								 | 
							
								    else
							 | 
						||
| 
								 | 
							
								      cb(err);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.mkdir = function(path, recursive, cb) { // MKD is optional
							 | 
						||
| 
								 | 
							
								  if (typeof recursive === 'function') {
							 | 
						||
| 
								 | 
							
								    cb = recursive;
							 | 
						||
| 
								 | 
							
								    recursive = false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (!recursive)
							 | 
						||
| 
								 | 
							
								    this._send('MKD ' + path, cb);
							 | 
						||
| 
								 | 
							
								  else {
							 | 
						||
| 
								 | 
							
								    var self = this, owd, abs, dirs, dirslen, i = -1, searching = true;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    abs = (path[0] === '/');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var nextDir = function() {
							 | 
						||
| 
								 | 
							
								      if (++i === dirslen) {
							 | 
						||
| 
								 | 
							
								        // return to original working directory
							 | 
						||
| 
								 | 
							
								        return self._send('CWD ' + owd, cb, true);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (searching) {
							 | 
						||
| 
								 | 
							
								        self._send('CWD ' + dirs[i], function(err, text, code) {
							 | 
						||
| 
								 | 
							
								          if (code === 550) {
							 | 
						||
| 
								 | 
							
								            searching = false;
							 | 
						||
| 
								 | 
							
								            --i;
							 | 
						||
| 
								 | 
							
								          } else if (err) {
							 | 
						||
| 
								 | 
							
								            // return to original working directory
							 | 
						||
| 
								 | 
							
								            return self._send('CWD ' + owd, function() {
							 | 
						||
| 
								 | 
							
								              cb(err);
							 | 
						||
| 
								 | 
							
								            }, true);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          nextDir();
							 | 
						||
| 
								 | 
							
								        }, true);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        self._send('MKD ' + dirs[i], function(err, text, code) {
							 | 
						||
| 
								 | 
							
								          if (err) {
							 | 
						||
| 
								 | 
							
								            // return to original working directory
							 | 
						||
| 
								 | 
							
								            return self._send('CWD ' + owd, function() {
							 | 
						||
| 
								 | 
							
								              cb(err);
							 | 
						||
| 
								 | 
							
								            }, true);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          self._send('CWD ' + dirs[i], nextDir, true);
							 | 
						||
| 
								 | 
							
								        }, true);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								    this.pwd(function(err, cwd) {
							 | 
						||
| 
								 | 
							
								      if (err)
							 | 
						||
| 
								 | 
							
								        return cb(err);
							 | 
						||
| 
								 | 
							
								      owd = cwd;
							 | 
						||
| 
								 | 
							
								      if (abs)
							 | 
						||
| 
								 | 
							
								        path = path.substr(1);
							 | 
						||
| 
								 | 
							
								      if (path[path.length - 1] === '/')
							 | 
						||
| 
								 | 
							
								        path = path.substring(0, path.length - 1);
							 | 
						||
| 
								 | 
							
								      dirs = path.split('/');
							 | 
						||
| 
								 | 
							
								      dirslen = dirs.length;
							 | 
						||
| 
								 | 
							
								      if (abs)
							 | 
						||
| 
								 | 
							
								        self._send('CWD /', function(err) {
							 | 
						||
| 
								 | 
							
								          if (err)
							 | 
						||
| 
								 | 
							
								            return cb(err);
							 | 
						||
| 
								 | 
							
								          nextDir();
							 | 
						||
| 
								 | 
							
								        }, true);
							 | 
						||
| 
								 | 
							
								      else
							 | 
						||
| 
								 | 
							
								        nextDir();
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.rmdir = function(path, recursive, cb) { // RMD is optional
							 | 
						||
| 
								 | 
							
								  if (typeof recursive === 'function') {
							 | 
						||
| 
								 | 
							
								    cb = recursive;
							 | 
						||
| 
								 | 
							
								    recursive = false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (!recursive) {
							 | 
						||
| 
								 | 
							
								    return this._send('RMD ' + path, cb);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  this.list(path, function(err, list) {
							 | 
						||
| 
								 | 
							
								    if (err) return cb(err);
							 | 
						||
| 
								 | 
							
								    var idx = 0;
							 | 
						||
| 
								 | 
							
								    
							 | 
						||
| 
								 | 
							
								    // this function will be called once per listing entry
							 | 
						||
| 
								 | 
							
								    var deleteNextEntry;
							 | 
						||
| 
								 | 
							
								    deleteNextEntry = function(err) {
							 | 
						||
| 
								 | 
							
								      if (err) return cb(err);
							 | 
						||
| 
								 | 
							
								      if (idx >= list.length) {
							 | 
						||
| 
								 | 
							
								        if (list[0] && list[0].name === path) {
							 | 
						||
| 
								 | 
							
								          return cb(null);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          return self.rmdir(path, cb);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      
							 | 
						||
| 
								 | 
							
								      var entry = list[idx++];
							 | 
						||
| 
								 | 
							
								      
							 | 
						||
| 
								 | 
							
								      // get the path to the file
							 | 
						||
| 
								 | 
							
								      var subpath = null;
							 | 
						||
| 
								 | 
							
								      if (entry.name[0] === '/') {
							 | 
						||
| 
								 | 
							
								        // this will be the case when you call deleteRecursively() and pass
							 | 
						||
| 
								 | 
							
								        // the path to a plain file
							 | 
						||
| 
								 | 
							
								        subpath = entry.name;
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        if (path[path.length - 1] == '/') {
							 | 
						||
| 
								 | 
							
								          subpath = path + entry.name;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          subpath = path + '/' + entry.name
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      
							 | 
						||
| 
								 | 
							
								      // delete the entry (recursively) according to its type
							 | 
						||
| 
								 | 
							
								      if (entry.type === 'd') {
							 | 
						||
| 
								 | 
							
								        if (entry.name === "." || entry.name === "..") {
							 | 
						||
| 
								 | 
							
								          return deleteNextEntry();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        self.rmdir(subpath, true, deleteNextEntry);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        self.delete(subpath, deleteNextEntry);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    deleteNextEntry();
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.system = function(cb) { // SYST is optional
							 | 
						||
| 
								 | 
							
								  this._send('SYST', function(err, text) {
							 | 
						||
| 
								 | 
							
								    if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								    cb(undefined, RE_SYST.exec(text)[1]);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// "Extended" (RFC 3659) commands
							 | 
						||
| 
								 | 
							
								FTP.prototype.size = function(path, cb) {
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  this._send('SIZE ' + path, function(err, text, code) {
							 | 
						||
| 
								 | 
							
								    if (code === 502) {
							 | 
						||
| 
								 | 
							
								      // Note: this may cause a problem as list() is _appended_ to the queue
							 | 
						||
| 
								 | 
							
								      return self.list(path, function(err, list) {
							 | 
						||
| 
								 | 
							
								        if (err)
							 | 
						||
| 
								 | 
							
								          return cb(err);
							 | 
						||
| 
								 | 
							
								        if (list.length === 1)
							 | 
						||
| 
								 | 
							
								          cb(undefined, list[0].size);
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								          // path could have been a directory and we got a listing of its
							 | 
						||
| 
								 | 
							
								          // contents, but here we echo the behavior of the real SIZE and
							 | 
						||
| 
								 | 
							
								          // return 'File not found' for directories
							 | 
						||
| 
								 | 
							
								          cb(new Error('File not found'));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    } else if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								    cb(undefined, parseInt(text, 10));
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.lastMod = function(path, cb) {
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  this._send('MDTM ' + path, function(err, text, code) {
							 | 
						||
| 
								 | 
							
								    if (code === 502) {
							 | 
						||
| 
								 | 
							
								      return self.list(path, function(err, list) {
							 | 
						||
| 
								 | 
							
								        if (err)
							 | 
						||
| 
								 | 
							
								          return cb(err);
							 | 
						||
| 
								 | 
							
								        if (list.length === 1)
							 | 
						||
| 
								 | 
							
								          cb(undefined, list[0].date);
							 | 
						||
| 
								 | 
							
								        else
							 | 
						||
| 
								 | 
							
								          cb(new Error('File not found'));
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    } else if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								    var val = XRegExp.exec(text, REX_TIMEVAL), ret;
							 | 
						||
| 
								 | 
							
								    if (!val)
							 | 
						||
| 
								 | 
							
								      return cb(new Error('Invalid date/time format from server'));
							 | 
						||
| 
								 | 
							
								    ret = new Date(val.year + '-' + val.month + '-' + val.date + 'T' + val.hour
							 | 
						||
| 
								 | 
							
								                   + ':' + val.minute + ':' + val.second);
							 | 
						||
| 
								 | 
							
								    cb(undefined, ret);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype.restart = function(offset, cb) {
							 | 
						||
| 
								 | 
							
								  this._send('REST ' + offset, cb);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Private/Internal methods
							 | 
						||
| 
								 | 
							
								FTP.prototype._pasv = function(cb) {
							 | 
						||
| 
								 | 
							
								  var self = this, first = true, ip, port;
							 | 
						||
| 
								 | 
							
								  this._send('PASV', function reentry(err, text) {
							 | 
						||
| 
								 | 
							
								    if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    self._curReq = undefined;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (first) {
							 | 
						||
| 
								 | 
							
								      var m = RE_PASV.exec(text);
							 | 
						||
| 
								 | 
							
								      if (!m)
							 | 
						||
| 
								 | 
							
								        return cb(new Error('Unable to parse PASV server response'));
							 | 
						||
| 
								 | 
							
								      ip = m[1];
							 | 
						||
| 
								 | 
							
								      ip += '.';
							 | 
						||
| 
								 | 
							
								      ip += m[2];
							 | 
						||
| 
								 | 
							
								      ip += '.';
							 | 
						||
| 
								 | 
							
								      ip += m[3];
							 | 
						||
| 
								 | 
							
								      ip += '.';
							 | 
						||
| 
								 | 
							
								      ip += m[4];
							 | 
						||
| 
								 | 
							
								      port = (parseInt(m[5], 10) * 256) + parseInt(m[6], 10);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      first = false;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    self._pasvConnect(ip, port, function(err, sock) {
							 | 
						||
| 
								 | 
							
								      if (err) {
							 | 
						||
| 
								 | 
							
								        // try the IP of the control connection if the server was somehow
							 | 
						||
| 
								 | 
							
								        // misconfigured and gave for example a LAN IP instead of WAN IP over
							 | 
						||
| 
								 | 
							
								        // the Internet
							 | 
						||
| 
								 | 
							
								        if (self._socket && ip !== self._socket.remoteAddress) {
							 | 
						||
| 
								 | 
							
								          ip = self._socket.remoteAddress;
							 | 
						||
| 
								 | 
							
								          return reentry();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // automatically abort PASV mode
							 | 
						||
| 
								 | 
							
								        self._send('ABOR', function() {
							 | 
						||
| 
								 | 
							
								          cb(err);
							 | 
						||
| 
								 | 
							
								          self._send();
							 | 
						||
| 
								 | 
							
								        }, true);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      cb(undefined, sock);
							 | 
						||
| 
								 | 
							
								      self._send();
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype._pasvConnect = function(ip, port, cb) {
							 | 
						||
| 
								 | 
							
								  var self = this,
							 | 
						||
| 
								 | 
							
								      socket = new Socket(),
							 | 
						||
| 
								 | 
							
								      sockerr,
							 | 
						||
| 
								 | 
							
								      timedOut = false,
							 | 
						||
| 
								 | 
							
								      timer = setTimeout(function() {
							 | 
						||
| 
								 | 
							
								        timedOut = true;
							 | 
						||
| 
								 | 
							
								        socket.destroy();
							 | 
						||
| 
								 | 
							
								        cb(new Error('Timed out while making data connection'));
							 | 
						||
| 
								 | 
							
								      }, this.options.pasvTimeout);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  socket.setTimeout(0);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  socket.once('connect', function() {
							 | 
						||
| 
								 | 
							
								    self._debug&&self._debug('[connection] PASV socket connected');
							 | 
						||
| 
								 | 
							
								    if (self.options.secure === true) {
							 | 
						||
| 
								 | 
							
								      self.options.secureOptions.socket = socket;
							 | 
						||
| 
								 | 
							
								      self.options.secureOptions.session = self._socket.getSession();
							 | 
						||
| 
								 | 
							
								      //socket.removeAllListeners('error');
							 | 
						||
| 
								 | 
							
								      socket = tls.connect(self.options.secureOptions);
							 | 
						||
| 
								 | 
							
								      //socket.once('error', onerror);
							 | 
						||
| 
								 | 
							
								      socket.setTimeout(0);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    clearTimeout(timer);
							 | 
						||
| 
								 | 
							
								    self._pasvSocket = socket;
							 | 
						||
| 
								 | 
							
								    cb(undefined, socket);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								  socket.once('error', onerror);
							 | 
						||
| 
								 | 
							
								  function onerror(err) {
							 | 
						||
| 
								 | 
							
								    sockerr = err;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  socket.once('end', function() {
							 | 
						||
| 
								 | 
							
								    clearTimeout(timer);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								  socket.once('close', function(had_err) {
							 | 
						||
| 
								 | 
							
								    clearTimeout(timer);
							 | 
						||
| 
								 | 
							
								    if (!self._pasvSocket && !timedOut) {
							 | 
						||
| 
								 | 
							
								      var errmsg = 'Unable to make data connection';
							 | 
						||
| 
								 | 
							
								      if (sockerr) {
							 | 
						||
| 
								 | 
							
								        errmsg += '( ' + sockerr + ')';
							 | 
						||
| 
								 | 
							
								        sockerr = undefined;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      cb(new Error(errmsg));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    self._pasvSocket = undefined;
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  socket.connect(port, ip);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype._store = function(cmd, input, zcomp, cb) {
							 | 
						||
| 
								 | 
							
								  var isBuffer = Buffer.isBuffer(input);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (!isBuffer && input.pause !== undefined)
							 | 
						||
| 
								 | 
							
								    input.pause();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof zcomp === 'function') {
							 | 
						||
| 
								 | 
							
								    cb = zcomp;
							 | 
						||
| 
								 | 
							
								    zcomp = false;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var self = this;
							 | 
						||
| 
								 | 
							
								  this._pasv(function(err, sock) {
							 | 
						||
| 
								 | 
							
								    if (err)
							 | 
						||
| 
								 | 
							
								      return cb(err);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (self._queue[0] && self._queue[0].cmd === 'ABOR') {
							 | 
						||
| 
								 | 
							
								      sock.destroy();
							 | 
						||
| 
								 | 
							
								      return cb();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var sockerr, dest = sock;
							 | 
						||
| 
								 | 
							
								    sock.once('error', function(err) {
							 | 
						||
| 
								 | 
							
								      sockerr = err;
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (zcomp) {
							 | 
						||
| 
								 | 
							
								      self._send('MODE Z', function(err, text, code) {
							 | 
						||
| 
								 | 
							
								        if (err) {
							 | 
						||
| 
								 | 
							
								          sock.destroy();
							 | 
						||
| 
								 | 
							
								          return cb(makeError(code, 'Compression not supported'));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        // draft-preston-ftpext-deflate-04 says min of 8 should be supported
							 | 
						||
| 
								 | 
							
								        dest = zlib.createDeflate({ level: 8 });
							 | 
						||
| 
								 | 
							
								        dest.pipe(sock);
							 | 
						||
| 
								 | 
							
								        sendStore();
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    } else
							 | 
						||
| 
								 | 
							
								      sendStore();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function sendStore() {
							 | 
						||
| 
								 | 
							
								      // this callback will be executed multiple times, the first is when server
							 | 
						||
| 
								 | 
							
								      // replies with 150, then a final reply after the data connection closes
							 | 
						||
| 
								 | 
							
								      // to indicate whether the transfer was actually a success or not
							 | 
						||
| 
								 | 
							
								      self._send(cmd, function(err, text, code) {
							 | 
						||
| 
								 | 
							
								        if (sockerr || err) {
							 | 
						||
| 
								 | 
							
								          if (zcomp) {
							 | 
						||
| 
								 | 
							
								            self._send('MODE S', function() {
							 | 
						||
| 
								 | 
							
								              cb(sockerr || err);
							 | 
						||
| 
								 | 
							
								            }, true);
							 | 
						||
| 
								 | 
							
								          } else
							 | 
						||
| 
								 | 
							
								            cb(sockerr || err);
							 | 
						||
| 
								 | 
							
								          return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (code === 150 || code === 125) {
							 | 
						||
| 
								 | 
							
								          if (isBuffer)
							 | 
						||
| 
								 | 
							
								            dest.end(input);
							 | 
						||
| 
								 | 
							
								          else if (typeof input === 'string') {
							 | 
						||
| 
								 | 
							
								            // check if input is a file path or just string data to store
							 | 
						||
| 
								 | 
							
								            fs.stat(input, function(err, stats) {
							 | 
						||
| 
								 | 
							
								              if (err)
							 | 
						||
| 
								 | 
							
								                dest.end(input);
							 | 
						||
| 
								 | 
							
								              else
							 | 
						||
| 
								 | 
							
								                fs.createReadStream(input).pipe(dest);
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            input.pipe(dest);
							 | 
						||
| 
								 | 
							
								            input.resume();
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          if (zcomp)
							 | 
						||
| 
								 | 
							
								            self._send('MODE S', cb, true);
							 | 
						||
| 
								 | 
							
								          else
							 | 
						||
| 
								 | 
							
								            cb();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }, true);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype._send = function(cmd, cb, promote) {
							 | 
						||
| 
								 | 
							
								  clearTimeout(this._keepalive);
							 | 
						||
| 
								 | 
							
								  if (cmd !== undefined) {
							 | 
						||
| 
								 | 
							
								    if (promote)
							 | 
						||
| 
								 | 
							
								      this._queue.unshift({ cmd: cmd, cb: cb });
							 | 
						||
| 
								 | 
							
								    else
							 | 
						||
| 
								 | 
							
								      this._queue.push({ cmd: cmd, cb: cb });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  var queueLen = this._queue.length;
							 | 
						||
| 
								 | 
							
								  if (!this._curReq && queueLen && this._socket && this._socket.readable) {
							 | 
						||
| 
								 | 
							
								    this._curReq = this._queue.shift();
							 | 
						||
| 
								 | 
							
								    if (this._curReq.cmd === 'ABOR' && this._pasvSocket)
							 | 
						||
| 
								 | 
							
								      this._pasvSocket.aborting = true;
							 | 
						||
| 
								 | 
							
								    this._debug&&this._debug('[connection] > ' + inspect(this._curReq.cmd));
							 | 
						||
| 
								 | 
							
								    this._socket.write(this._curReq.cmd + '\r\n');
							 | 
						||
| 
								 | 
							
								  } else if (!this._curReq && !queueLen && this._ending)
							 | 
						||
| 
								 | 
							
								    this._reset();
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FTP.prototype._reset = function() {
							 | 
						||
| 
								 | 
							
								  if (this._pasvSock && this._pasvSock.writable)
							 | 
						||
| 
								 | 
							
								    this._pasvSock.end();
							 | 
						||
| 
								 | 
							
								  if (this._socket && this._socket.writable)
							 | 
						||
| 
								 | 
							
								    this._socket.end();
							 | 
						||
| 
								 | 
							
								  this._socket = undefined;
							 | 
						||
| 
								 | 
							
								  this._pasvSock = undefined;
							 | 
						||
| 
								 | 
							
								  this._feat = undefined;
							 | 
						||
| 
								 | 
							
								  this._curReq = undefined;
							 | 
						||
| 
								 | 
							
								  this._secstate = undefined;
							 | 
						||
| 
								 | 
							
								  clearTimeout(this._keepalive);
							 | 
						||
| 
								 | 
							
								  this._keepalive = undefined;
							 | 
						||
| 
								 | 
							
								  this._queue = [];
							 | 
						||
| 
								 | 
							
								  this._ending = false;
							 | 
						||
| 
								 | 
							
								  this._parser = undefined;
							 | 
						||
| 
								 | 
							
								  this.options.host = this.options.port = this.options.user
							 | 
						||
| 
								 | 
							
								                    = this.options.password = this.options.secure
							 | 
						||
| 
								 | 
							
								                    = this.options.connTimeout = this.options.pasvTimeout
							 | 
						||
| 
								 | 
							
								                    = this.options.keepalive = this._debug = undefined;
							 | 
						||
| 
								 | 
							
								  this.connected = false;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Utility functions
							 | 
						||
| 
								 | 
							
								function makeError(code, text) {
							 | 
						||
| 
								 | 
							
								  var err = new Error(text);
							 | 
						||
| 
								 | 
							
								  err.code = code;
							 | 
						||
| 
								 | 
							
								  return err;
							 | 
						||
| 
								 | 
							
								}
							 |