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.
		
		
		
		
		
			
		
			
				
					217 lines
				
				7.4 KiB
			
		
		
			
		
	
	
					217 lines
				
				7.4 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								var WritableStream = require('stream').Writable
							 | 
						||
| 
								 | 
							
								                     || require('readable-stream').Writable,
							 | 
						||
| 
								 | 
							
								    inherits = require('util').inherits,
							 | 
						||
| 
								 | 
							
								    inspect = require('util').inspect;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var XRegExp = require('xregexp').XRegExp;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var REX_LISTUNIX = XRegExp.cache('^(?<type>[\\-ld])(?<permission>([\\-r][\\-w][\\-xstT]){3})(?<acl>(\\+))?\\s+(?<inodes>\\d+)\\s+(?<owner>\\S+)\\s+(?<group>\\S+)\\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>.+)$'),
							 | 
						||
| 
								 | 
							
								    RE_ENTRY_TOTAL = /^total/,
							 | 
						||
| 
								 | 
							
								    RE_RES_END = /(?:^|\r?\n)(\d{3}) [^\r\n]*\r?\n/,
							 | 
						||
| 
								 | 
							
								    RE_EOL = /\r?\n/g,
							 | 
						||
| 
								 | 
							
								    RE_DASH = /\-/g;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function Parser(options) {
							 | 
						||
| 
								 | 
							
								  if (!(this instanceof Parser))
							 | 
						||
| 
								 | 
							
								    return new Parser(options);
							 | 
						||
| 
								 | 
							
								  WritableStream.call(this);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._buffer = '';
							 | 
						||
| 
								 | 
							
								  this._debug = options.debug;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								inherits(Parser, WritableStream);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Parser.prototype._write = function(chunk, encoding, cb) {
							 | 
						||
| 
								 | 
							
								  var m, code, reRmLeadCode, rest = '', debug = this._debug;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this._buffer += chunk.toString('binary');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while (m = RE_RES_END.exec(this._buffer)) {
							 | 
						||
| 
								 | 
							
								    // support multiple terminating responses in the buffer
							 | 
						||
| 
								 | 
							
								    rest = this._buffer.substring(m.index + m[0].length);
							 | 
						||
| 
								 | 
							
								    if (rest.length)
							 | 
						||
| 
								 | 
							
								      this._buffer = this._buffer.substring(0, m.index + m[0].length);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    debug&&debug('[parser] < ' + inspect(this._buffer));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // we have a terminating response line
							 | 
						||
| 
								 | 
							
								    code = parseInt(m[1], 10);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // 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 = new RegExp(reRmLeadCode, 'g');
							 | 
						||
| 
								 | 
							
								    var text = this._buffer.replace(reRmLeadCode, '$1').trim();
							 | 
						||
| 
								 | 
							
								    this._buffer = rest;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    debug&&debug('[parser] Response: code=' + code + ', buffer=' + inspect(text));
							 | 
						||
| 
								 | 
							
								    this.emit('response', code, text);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  cb();
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Parser.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 return the raw lines for now
							 | 
						||
| 
								 | 
							
								  return lines;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Parser.parseListEntry = function(line) {
							 | 
						||
| 
								 | 
							
								  var ret,
							 | 
						||
| 
								 | 
							
								      info,
							 | 
						||
| 
								 | 
							
								      month, day, year,
							 | 
						||
| 
								 | 
							
								      hour, mins;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (ret = XRegExp.exec(line, REX_LISTUNIX)) {
							 | 
						||
| 
								 | 
							
								    info = {
							 | 
						||
| 
								 | 
							
								      type: ret.type,
							 | 
						||
| 
								 | 
							
								      name: undefined,
							 | 
						||
| 
								 | 
							
								      target: undefined,
							 | 
						||
| 
								 | 
							
								      sticky: false,
							 | 
						||
| 
								 | 
							
								      rights: {
							 | 
						||
| 
								 | 
							
								        user: ret.permission.substr(0, 3).replace(RE_DASH, ''),
							 | 
						||
| 
								 | 
							
								        group: ret.permission.substr(3, 3).replace(RE_DASH, ''),
							 | 
						||
| 
								 | 
							
								        other: ret.permission.substr(6, 3).replace(RE_DASH, '')
							 | 
						||
| 
								 | 
							
								      },
							 | 
						||
| 
								 | 
							
								      acl: (ret.acl === '+'),
							 | 
						||
| 
								 | 
							
								      owner: ret.owner,
							 | 
						||
| 
								 | 
							
								      group: ret.group,
							 | 
						||
| 
								 | 
							
								      size: parseInt(ret.size, 10),
							 | 
						||
| 
								 | 
							
								      date: undefined
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // check for sticky bit
							 | 
						||
| 
								 | 
							
								    var lastbit = info.rights.other.slice(-1);
							 | 
						||
| 
								 | 
							
								    if (lastbit === 't') {
							 | 
						||
| 
								 | 
							
								      info.rights.other = info.rights.other.slice(0, -1) + 'x';
							 | 
						||
| 
								 | 
							
								      info.sticky = true;
							 | 
						||
| 
								 | 
							
								    } else if (lastbit === 'T') {
							 | 
						||
| 
								 | 
							
								      info.rights.other = info.rights.other.slice(0, -1);
							 | 
						||
| 
								 | 
							
								      info.sticky = true;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    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);
							 | 
						||
| 
								 | 
							
								      // If the date is in the past but no more than 6 months old, year
							 | 
						||
| 
								 | 
							
								      // isn't displayed and doesn't have to be the current year.
							 | 
						||
| 
								 | 
							
								      // 
							 | 
						||
| 
								 | 
							
								      // If the date is in the future (less than an hour from now), year
							 | 
						||
| 
								 | 
							
								      // isn't displayed and doesn't have to be the current year.
							 | 
						||
| 
								 | 
							
								      // That second case is much more rare than the first and less annoying.
							 | 
						||
| 
								 | 
							
								      // It's impossible to fix without knowing about the server's timezone,
							 | 
						||
| 
								 | 
							
								      // so we just don't do anything about it.
							 | 
						||
| 
								 | 
							
								      // 
							 | 
						||
| 
								 | 
							
								      // If we're here with a time that is more than 28 hours into the
							 | 
						||
| 
								 | 
							
								      // future (1 hour + maximum timezone offset which is 27 hours),
							 | 
						||
| 
								 | 
							
								      // there is a problem -- we should be in the second conditional block
							 | 
						||
| 
								 | 
							
								      if (info.date.getTime() - Date.now() > 100800000) {
							 | 
						||
| 
								 | 
							
								        info.date = new Date((year - 1) + '-'
							 | 
						||
| 
								 | 
							
								                             + month + '-'
							 | 
						||
| 
								 | 
							
								                             + day + 'T'
							 | 
						||
| 
								 | 
							
								                             + hour + ':'
							 | 
						||
| 
								 | 
							
								                             + mins);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // If we're here with a time that is more than 6 months old, there's
							 | 
						||
| 
								 | 
							
								      // a problem as well.
							 | 
						||
| 
								 | 
							
								      // Maybe local & remote servers aren't on the same timezone (with remote
							 | 
						||
| 
								 | 
							
								      // ahead of local)
							 | 
						||
| 
								 | 
							
								      // For instance, remote is in 2014 while local is still in 2013. In
							 | 
						||
| 
								 | 
							
								      // this case, a date like 01/01/13 02:23 could be detected instead of
							 | 
						||
| 
								 | 
							
								      // 01/01/14 02:23 
							 | 
						||
| 
								 | 
							
								      // Our trigger point will be 3600*24*31*6 (since we already use 31
							 | 
						||
| 
								 | 
							
								      // as an upper bound, no need to add the 27 hours timezone offset)
							 | 
						||
| 
								 | 
							
								      if (Date.now() - info.date.getTime() > 16070400000) {
							 | 
						||
| 
								 | 
							
								        info.date = new Date((year + 1) + '-'
							 | 
						||
| 
								 | 
							
								                             + 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 if (!RE_ENTRY_TOTAL.test(line))
							 | 
						||
| 
								 | 
							
								    ret = line; // could not parse, so at least give the end user a chance to
							 | 
						||
| 
								 | 
							
								                // look at the raw listing themselves
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return ret;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = Parser;
							 |