Permalink
Browse files

separate out response parsing and a couple other parsing functions

  • Loading branch information...
1 parent 42c1d33 commit 9e3b1d07e54a716ceccc120cbc664aa3cf4f3764 @mscdex committed Apr 18, 2013
Showing with 214 additions and 180 deletions.
  1. +47 −178 lib/{ftp.js → connection.js}
  2. +164 −0 lib/parser.js
  3. +3 −2 package.json
@@ -6,23 +6,17 @@ var fs = require('fs'),
inherits = require('util').inherits,
inspect = require('util').inspect;
+var Parser = require('./parser');
var XRegExp = require('xregexp').XRegExp;
-var REX_LISTUNIX = XRegExp.cache('^(?<type>[\\-ld])(?<permission>([\\-r][\\-w][\\-xs]){3})\\s+(?<inodes>\\d+)\\s+(?<owner>\\w+)\\s+(?<group>\\w+)\\s+(?<size>\\d+)\\s+(?<timestamp>((?<month1>\\w{3})\\s+(?<date1>\\d{1,2})\\s+(?<hour>\\d{1,2}):(?<minute>\\d{2}))|((?<month2>\\w{3})\\s+(?<date2>\\d{1,2})\\s+(?<year>\\d{4})))\\s+(?<name>.+)$'),
- REX_LISTMSDOS = XRegExp.cache('^(?<month>\\d{2})(?:\\-|\\/)(?<date>\\d{2})(?:\\-|\\/)(?<year>\\d{2,4})\\s+(?<hour>\\d{2}):(?<minute>\\d{2})\\s{0,1}(?<ampm>[AaMmPp]{1,2})\\s+(?:(?<size>\\d+)|(?<isdir>\\<DIR\\>))\\s+(?<name>.+)$'),
- REX_TIMEVAL = XRegExp.cache('^(?<year>\\d{4})(?<month>\\d{2})(?<date>\\d{2})(?<hour>\\d{2})(?<minute>\\d{2})(?<second>\\d+)(?:.\\d+)?$'),
+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_CWD = /"(.+)"(?: |$)/,
RE_PWD = /^"(.+)"(?: |$)/,
- RE_SYST = /^([^ ]+)(?: |$)/,
- RE_RES_END = /(?:^|\r?\n)(\d{3}) [^\r\n]*\r?\n/;
+ RE_SYST = /^([^ ]+)(?: |$)/;
-var MONTHS = {
- jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6,
- jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12
- },
- TYPE = {
+var TYPE = {
SYNTAX: 0,
INFO: 1,
SOCKETS: 2,
@@ -65,11 +59,11 @@ var FTP = module.exports = function() {
this._feat = undefined;
this._curReq = undefined;
this._queue = [];
- this._buffer = '';
this._secstate = undefined;
this._debug = undefined;
this._keepalive = undefined;
this._ending = false;
+ this._parser = undefined;
this.options = {
host: undefined,
port: undefined,
@@ -104,6 +98,30 @@ FTP.prototype.connect = function(options) {
var debug = this._debug;
var socket = this._socket = new Socket();
+ 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();
+ });
+
this._socket.setTimeout(0);
if (this.options.secure === 'implicit')
socket = tls.connect({ socket: this._socket }, onconnect);
@@ -129,7 +147,7 @@ FTP.prototype.connect = function(options) {
clearTimeout(self._keepalive);
else if (!self._curReq && self._queue.length === 0) {
self._curReq = noopreq;
- debug&&debug('> NOOP');
+ debug&&debug('[connection] > NOOP');
self._socket.write(bytesNOOP);
} else
noopreq.cb();
@@ -167,7 +185,7 @@ FTP.prototype.connect = function(options) {
|| (cmd === 'AUTH SSL' && code !== 334)
|| (cmd === 'PBSZ' && code !== 200)
|| (cmd === 'PROT' && code !== 200)) {
- self.emit('error', makeError('Unable to secure connection(s)', code));
+ self.emit('error', makeError(code, 'Unable to secure connection(s)'));
return self._socket.end();
}
@@ -187,7 +205,7 @@ FTP.prototype.connect = function(options) {
if (code === 331) {
// password required
if (!self.options.password) {
- self.emit('error', makeError('Password required', code));
+ self.emit('error', makeError(code, 'Password required'));
return self._socket.end();
}
cmd = 'PASS';
@@ -202,7 +220,7 @@ FTP.prototype.connect = function(options) {
self._send(cmd, reentry, true);
} else if (cmd === 'FEAT') {
if (!err)
- self._parseFeat(text);
+ self._feat = Parser.parseFeat(text);
cmd = 'TYPE';
self._send('TYPE I', reentry, true);
} else if (cmd === 'TYPE')
@@ -231,61 +249,14 @@ FTP.prototype.connect = function(options) {
socket.once('end', onend);
}
}
- };
+ }
- socket.setEncoding('binary');
socket.on('data', ondata);
function ondata(chunk) {
- self._buffer += chunk;
- var m;
- while (m = RE_RES_END.exec(self._buffer)) {
- var code, retval, reRmLeadCode, rest;
-
- // support multiple terminating responses in the buffer
- rest = self._buffer.substring(m.index + m[0].length);
- if (rest.length)
- self._buffer = self._buffer.substring(0, m.index + m[0].length);
-
- debug&&debug('< ' + inspect(self._buffer));
-
- // we have a terminating response line
- code = parseInt(m[1], 10);
- retval = code / 100 >> 0;
-
- // RFC 959 does not require each line in a multi-line response to begin
- // with '<code>-', but many servers will do this.
- //
- // remove this leading '<code>-' (or '<code> ' from last line) from each
- // line in the response ...
- reRmLeadCode = '(^|\\r?\\n)';
- reRmLeadCode += m[1];
- reRmLeadCode += '(?: |\\-)';
- reRmLeadCode = RegExp(reRmLeadCode, 'g');
- self._buffer = self._buffer.replace(reRmLeadCode, '$1').trim();
-
- debug&&debug('Parsed response: code=' + code + '; buffer=' + inspect(self._buffer));
- if (retval === RETVAL.ERR_TEMP || retval === RETVAL.ERR_PERM) {
- if (self._curReq)
- self._curReq.cb(makeError(self._buffer, code), undefined, code);
- else
- self.emit('error', makeError(self._buffer, code));
- } else if (self._curReq)
- self._curReq.cb(undefined, self._buffer, code);
- self._buffer = rest;
-
- // 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();
- }
- };
+ debug&&debug('[connection] < ' + inspect(chunk.toString('binary')));
+ if (self._parser)
+ self._parser.write(chunk);
+ }
this._socket.once('error', function(err) {
clearTimeout(timer);
@@ -437,7 +408,7 @@ FTP.prototype.list = function(path, zcomp, cb) {
entries = buffer.split(RE_EOL);
entries.pop(); // ending EOL
for (var i = 0, len = entries.length; i < len; ++i)
- entries[i] = parseListEntry(entries[i]);
+ entries[i] = Parser.parseListEntry(entries[i]);
if (zcomp) {
self._send('MODE S', function() {
@@ -452,7 +423,7 @@ FTP.prototype.list = function(path, zcomp, cb) {
self._send('MODE Z', function(err, text, code) {
if (err) {
sock.destroy();
- return cb(makeError('Compression not supported', code));
+ return cb(makeError(code, 'Compression not supported'));
}
sendList();
}, true);
@@ -554,7 +525,7 @@ FTP.prototype.get = function(path, zcomp, cb) {
self._send('MODE Z', function(err, text, code) {
if (err) {
sock.destroy();
- return cb(makeError('Compression not supported', code));
+ return cb(makeError(code, 'Compression not supported'));
}
sendRetr();
}, true);
@@ -607,7 +578,7 @@ FTP.prototype.append = function(input, path, zcomp, cb) {
FTP.prototype.pwd = function(cb) { // PWD is optional
var self = this;
this._send('PWD', function(err, text, code) {
- if (code == 502) {
+ if (code === 502) {
return self.cwd('.', function(cwderr, cwd) {
if (cwderr)
return cb(cwderr);
@@ -765,18 +736,6 @@ FTP.prototype.restart = function(offset, cb) {
// Private/Internal methods
-FTP.prototype._parseFeat = function(text) {
- var lines = text.split(RE_EOL);
- lines.shift(); // initial response line
- lines.pop(); // final response line
-
- for (var i = 0, len = lines.length; i < len; ++i)
- lines[i] = lines[i].trim();
-
- // just store the raw lines for now
- this._feat = lines;
-};
-
FTP.prototype._pasv = function(cb) {
var self = this, first = true, ip, port;
this._send('PASV', function reentry(err, text) {
@@ -901,7 +860,7 @@ FTP.prototype._store = function(cmd, input, zcomp, cb) {
self._send('MODE Z', function(err, text, code) {
if (err) {
sock.destroy();
- return cb(makeError('Compression not supported', code));
+ return cb(makeError(code, 'Compression not supported'));
}
// draft-preston-ftpext-deflate-04 says min of 8 should be supported
dest = zlib.createDeflate({ level: 8 });
@@ -965,7 +924,7 @@ FTP.prototype._send = function(cmd, cb, promote) {
this._curReq = this._queue.shift();
if (this._curReq.cmd === 'ABOR' && this._pasvSocket)
this._pasvSocket.aborting = true;
- this._debug&&this._debug('> ' + inspect(this._curReq.cmd));
+ this._debug&&this._debug('[connection] > ' + inspect(this._curReq.cmd));
this._socket.write(this._curReq.cmd);
this._socket.write(bytesCRLF);
} else if (!this._curReq && !queueLen && this._ending)
@@ -985,8 +944,8 @@ FTP.prototype._reset = function() {
clearTimeout(this._keepalive);
this._keepalive = undefined;
this._queue = [];
- this._buffer = '';
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
@@ -995,98 +954,8 @@ FTP.prototype._reset = function() {
};
// Utility functions
-function parseListEntry(line) {
- var ret,
- info,
- month,
- day,
- year,
- hour,
- mins;
-
- if (ret = XRegExp.exec(line, REX_LISTUNIX)) {
- info = {
- type: ret.type,
- name: undefined,
- target: undefined,
- rights: {
- user: ret.permission.substr(0, 3).replace(/\-/g, ''),
- group: ret.permission.substr(3, 3).replace(/\-/g, ''),
- other: ret.permission.substr(6, 3).replace(/\-/g, '')
- },
- owner: ret.owner,
- group: ret.group,
- size: parseInt(ret.size, 10),
- date: undefined
- };
- if (ret.month1 !== undefined) {
- month = parseInt(MONTHS[ret.month1.toLowerCase()], 10);
- day = parseInt(ret.date1, 10);
- year = (new Date()).getFullYear();
- hour = parseInt(ret.hour, 10);
- mins = parseInt(ret.minute, 10);
- if (month < 10)
- month = '0' + month;
- if (day < 10)
- day = '0' + day;
- if (hour < 10)
- hour = '0' + hour;
- if (mins < 10)
- mins = '0' + mins;
- info.date = new Date(year + '-' + month + '-' + day
- + 'T' + hour + ':' + mins);
- } else if (ret.month2 !== undefined) {
- month = parseInt(MONTHS[ret.month2.toLowerCase()], 10);
- day = parseInt(ret.date2, 10);
- year = parseInt(ret.year, 10);
- if (month < 10)
- month = '0' + month;
- if (day < 10)
- day = '0' + day;
- info.date = new Date(year + '-' + month + '-' + day);
- }
- if (ret.type === 'l') {
- var pos = ret.name.indexOf(' -> ');
- info.name = ret.name.substring(0, pos);
- info.target = ret.name.substring(pos+4);
- } else
- info.name = ret.name;
- ret = info;
- } else if (ret = XRegExp.exec(line, REX_LISTMSDOS)) {
- info = {
- name: ret.name,
- type: (ret.isdir ? 'd' : '-'),
- size: (ret.isdir ? 0 : parseInt(ret.size, 10)),
- date: undefined,
- };
- month = parseInt(ret.month, 10),
- day = parseInt(ret.date, 10),
- year = parseInt(ret.year, 10),
- hour = parseInt(ret.hour, 10),
- mins = parseInt(ret.minute, 10);
-
- if (year < 70)
- year += 2000;
- else
- year += 1900;
-
- if (ret.ampm[0].toLowerCase() === 'p' && hour < 12)
- hour += 12;
- else if (ret.ampm[0].toLowerCase() === 'a' && hour === 12)
- hour = 0;
-
- info.date = new Date(year, month - 1, day, hour, mins)
-
- ret = info;
- } else
- ret = line; // could not parse, so at least give the end user a chance to
- // look at the raw listing themselves
-
- return ret;
-}
-
-function makeError(msg, code) {
- var err = new Error(msg);
+function makeError(code, text) {
+ var err = new Error(text);
err.code = code;
return err;
}
Oops, something went wrong.

0 comments on commit 9e3b1d0

Please sign in to comment.