@icetee/ftp
Advanced tools
Comparing version 0.3.15 to 1.0.0
@@ -1,21 +0,23 @@ | ||
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, | ||
StringDecoder = require('string_decoder').StringDecoder; | ||
const fs = require('fs'); | ||
const tls = require('tls'); | ||
const zlib = require('zlib'); | ||
const XRegExp = require('xregexp'); | ||
var Parser = require('./parser'); | ||
var XRegExp = require('xregexp').XRegExp; | ||
const { Socket } = require('net'); | ||
const { EventEmitter } = require('events'); | ||
const { inherits, inspect } = require('util'); | ||
const { StringDecoder } = require('string_decoder'); | ||
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_EPSV = /([\d]+)/, | ||
RE_EOL = /\r?\n/g, | ||
RE_WD = /"(.+)"(?: |$)/, | ||
RE_SYST = /^([^ ]+)(?: |$)/; | ||
const Parser = require('./parser'); | ||
const { | ||
REX_TIMEVAL, | ||
RE_PASV, | ||
RE_EPSV, | ||
RE_EOL, | ||
RE_WD, | ||
RE_SYST, | ||
RETVAL, | ||
} = require('./expressions'); | ||
var /*TYPE = { | ||
const /* TYPE = { | ||
SYNTAX: 0, | ||
@@ -28,10 +30,3 @@ INFO: 1, | ||
},*/ | ||
RETVAL = { | ||
PRELIM: 1, | ||
OK: 2, | ||
WAITING: 3, | ||
ERR_TEMP: 4, | ||
ERR_PERM: 5 | ||
}, | ||
/*ERRORS = { | ||
/* ERRORS = { | ||
421: 'Service not available, closing control connection', | ||
@@ -38,0 +33,0 @@ 425: 'Can\'t open data connection', |
@@ -1,244 +0,251 @@ | ||
var WritableStream = require('stream').Writable | ||
|| require('readable-stream').Writable, | ||
inherits = require('util').inherits, | ||
inspect = require('util').inspect; | ||
/* eslint prefer-destructuring: ["error", {VariableDeclarator: {object: true}}] */ | ||
var XRegExp = require('xregexp').XRegExp; | ||
const XRegExp = require('xregexp'); | ||
const WritableStream = require('stream').Writable; | ||
const { EventEmitter } = require('events'); | ||
const { inherits, inspect } = require('util'); | ||
const { | ||
REX_LISTUNIX, | ||
REX_LISTMSDOS, | ||
RE_ENTRY_TOTAL, | ||
RE_RES_END, | ||
RE_EOL, | ||
RE_DASH, | ||
RE_SEP, | ||
RE_EQ, | ||
MONTHS, | ||
} = require('./expressions'); | ||
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, | ||
RE_SEP = /;/g, | ||
RE_EQ = /=/; | ||
class Parser extends EventEmitter { | ||
constructor(options) { | ||
super(); | ||
if (!(this instanceof Parser)) return new Parser(options); | ||
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 | ||
}; | ||
WritableStream.call(this); | ||
function Parser(options) { | ||
if (!(this instanceof Parser)) | ||
return new Parser(options); | ||
WritableStream.call(this); | ||
this.buffer = ''; | ||
this.debug = options.debug; | ||
} | ||
this._buffer = ''; | ||
this._debug = options.debug; | ||
} | ||
inherits(Parser, WritableStream); | ||
static parseFeat(text) { | ||
const lines = text.split(RE_EOL); | ||
lines.shift(); // initial response line | ||
lines.pop(); // final response line | ||
Parser.prototype._write = function(chunk, encoding, cb) { | ||
var m, code, reRmLeadCode, rest = '', debug = this._debug; | ||
for (let i = 0, len = lines.length; i < len; ++i) { | ||
lines[i] = lines[i].trim(); | ||
} | ||
this._buffer += chunk.toString('binary'); | ||
return lines; | ||
} | ||
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); | ||
write(chunk, encoding, cb) { | ||
let m; | ||
let code; | ||
let reRmLeadCode; | ||
let rest = ''; | ||
debug&&debug('[parser] < ' + inspect(this._buffer)); | ||
this.buffer += chunk.toString('binary'); | ||
// we have a terminating response line | ||
code = parseInt(m[1], 10); | ||
while (RE_RES_END.exec(this.buffer) !== null) { | ||
m = RE_RES_END.exec(this.buffer); | ||
// support multiple terminating responses in the buffer | ||
rest = this.buffer.substring(m.index + m[0].length); | ||
// 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; | ||
if (rest.length) { | ||
this.buffer = this.buffer.substring(0, m.index + m[0].length); | ||
} | ||
debug&&debug('[parser] Response: code=' + code + ', buffer=' + inspect(text)); | ||
this.emit('response', code, text); | ||
if (this.debug) console.error(`[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'); | ||
const text = this.buffer.replace(reRmLeadCode, '$1').trim(); | ||
this.buffer = rest; | ||
if (this.debug) console.error(`[parser] Response: code=${code}, buffer=${inspect(text)}`); | ||
this.emit('response', code, text); | ||
} | ||
if (typeof cb === 'function') { | ||
cb(); | ||
} | ||
} | ||
cb(); | ||
}; | ||
static parseListEntry(line) { | ||
let ret = null; | ||
let info; | ||
let month; | ||
let day; | ||
let year; | ||
let hour; | ||
let mins; | ||
const retUnix = XRegExp.exec(line, REX_LISTUNIX); | ||
const retMsdos = XRegExp.exec(line, REX_LISTMSDOS); | ||
Parser.parseFeat = function(text) { | ||
var lines = text.split(RE_EOL); | ||
lines.shift(); // initial response line | ||
lines.pop(); // final response line | ||
if (retUnix) { | ||
info = { | ||
type: retUnix.type, | ||
name: undefined, | ||
target: undefined, | ||
sticky: false, | ||
rights: { | ||
user: retUnix.permission.substr(0, 3).replace(RE_DASH, ''), | ||
group: retUnix.permission.substr(3, 3).replace(RE_DASH, ''), | ||
other: retUnix.permission.substr(6, 3).replace(RE_DASH, ''), | ||
}, | ||
acl: (retUnix.acl === '+'), | ||
owner: retUnix.owner, | ||
group: retUnix.group, | ||
size: parseInt(retUnix.size, 10), | ||
date: undefined, | ||
}; | ||
for (var i = 0, len = lines.length; i < len; ++i) | ||
lines[i] = lines[i].trim(); | ||
// Check for sticky bit | ||
const lastbit = info.rights.other.slice(-1); | ||
// just return the raw lines for now | ||
return lines; | ||
}; | ||
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; | ||
} | ||
Parser.parseListEntry = function(line) { | ||
var ret, | ||
info, | ||
month, day, year, | ||
hour, mins; | ||
if (retUnix.month1 !== undefined) { | ||
month = parseInt(MONTHS[retUnix.month1.toLowerCase()], 10); | ||
day = parseInt(retUnix.date1, 10); | ||
year = (new Date()).getFullYear(); | ||
hour = parseInt(retUnix.hour, 10); | ||
mins = parseInt(retUnix.minute, 10); | ||
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 | ||
}; | ||
if (month < 10) month = `0${month}`; | ||
if (day < 10) day = `0${day}`; | ||
if (hour < 10) hour = `0${hour}`; | ||
if (mins < 10) mins = `0${mins}`; | ||
// 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; | ||
} | ||
info.date = new Date(`${year}-${month}-${day}T${hour}:${mins}`); | ||
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 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 (retUnix.month2 !== undefined) { | ||
month = parseInt(MONTHS[retUnix.month2.toLowerCase()], 10); | ||
day = parseInt(retUnix.date2, 10); | ||
year = parseInt(retUnix.year, 10); | ||
if (month < 10) month = `0${month}`; | ||
if (day < 10) day = `0${day}`; | ||
info.date = new Date(`${year}-${month}-${day}`); | ||
} | ||
// 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); | ||
if (retUnix.type === 'l') { | ||
const pos = retUnix.name.indexOf(' -> '); | ||
info.name = retUnix.name.substring(0, pos); | ||
info.target = retUnix.name.substring(pos + 4); | ||
} else { | ||
info.name = retUnix.name; | ||
} | ||
} 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; | ||
ret = info; | ||
} else if (retMsdos) { | ||
info = { | ||
name: retMsdos.name, | ||
type: (retMsdos.isdir ? 'd' : '-'), | ||
size: (retMsdos.isdir ? 0 : parseInt(retMsdos.size, 10)), | ||
date: undefined, | ||
}; | ||
if (ret.ampm[0].toLowerCase() === 'p' && hour < 12) | ||
hour += 12; | ||
else if (ret.ampm[0].toLowerCase() === 'a' && hour === 12) | ||
hour = 0; | ||
month = parseInt(retMsdos.month, 10); | ||
day = parseInt(retMsdos.date, 10); | ||
year = parseInt(retMsdos.year, 10); | ||
hour = parseInt(retMsdos.hour, 10); | ||
mins = parseInt(retMsdos.minute, 10); | ||
info.date = new Date(year, month - 1, day, hour, mins); | ||
year += (year < 70) ? 2000 : 1900; | ||
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 | ||
if (retMsdos.ampm[0].toLowerCase() === 'p' && hour < 12) { | ||
hour += 12; | ||
} else if (retMsdos.ampm[0].toLowerCase() === 'a' && hour === 12) { | ||
hour = 0; | ||
} | ||
return ret; | ||
}; | ||
info.date = new Date(year, month - 1, day, hour, mins); | ||
Parser.parseMlsdEntry = function(entry) { | ||
var kvs = entry.split(RE_SEP); | ||
var obj = { name: kvs.pop().substring(1) }; | ||
kvs.forEach(function(kv) { | ||
kv = kv.split( RE_EQ ); | ||
obj[kv[0].toLowerCase()] = kv[1]; | ||
}); | ||
ret = info; | ||
} else if (!RE_ENTRY_TOTAL.test(line)) { | ||
// could not parse, so at least give the end user a chance to | ||
// look at the raw listing themselves | ||
ret = line; | ||
} | ||
obj.size = parseInt(obj.size, 10); | ||
var modify = obj.modify; | ||
if (modify) { | ||
var year = modify.substr(0, 4); | ||
var month = modify.substr(4, 2); | ||
var date = modify.substr(6, 2); | ||
var hour = modify.substr(8, 2); | ||
var minute = modify.substr(10, 2); | ||
var second = modify.substr(12, 2); | ||
obj.date = new Date( | ||
year + '-' + month + '-' + date + 'T' + hour + ':' +minute + ':' + second | ||
); | ||
return ret; | ||
} | ||
static parseMlsdEntry(entry) { | ||
const kvs = entry.split(RE_SEP); | ||
const obj = { name: kvs.pop().substring(1) }; | ||
kvs.forEach((kv) => { | ||
kv = kv.split(RE_EQ); | ||
obj[kv[0].toLowerCase()] = kv[1]; | ||
}); | ||
obj.size = parseInt(obj.size, 10); | ||
if (obj.modify) { | ||
const year = obj.modify.substr(0, 4); | ||
const month = obj.modify.substr(4, 2); | ||
const date = obj.modify.substr(6, 2); | ||
const hour = obj.modify.substr(8, 2); | ||
const minute = obj.modify.substr(10, 2); | ||
const second = obj.modify.substr(12, 2); | ||
obj.date = new Date(`${year}-${month}-${date}T${hour}:${minute}:${second}`); | ||
} | ||
return obj; | ||
}; | ||
} | ||
} | ||
inherits(Parser, WritableStream); | ||
module.exports = Parser; |
{ | ||
"name": "@icetee/ftp", | ||
"version": "0.3.15", | ||
"version": "1.0.0", | ||
"author": "Tamás András Horváth <htomy92@gmail.com>", | ||
@@ -11,10 +11,9 @@ "contributors": [ | ||
"engines": { | ||
"node": ">=0.8.0" | ||
"node": ">=6.4.0" | ||
}, | ||
"dependencies": { | ||
"xregexp": "2.0.0", | ||
"readable-stream": "1.1.x" | ||
"xregexp": "^3.2.0" | ||
}, | ||
"scripts": { | ||
"test": "node test/test.js" | ||
"test": "node ./node_modules/mocha/bin/_mocha --require babel-register ./test/*.spec.js" | ||
}, | ||
@@ -26,12 +25,18 @@ "keywords": [ | ||
], | ||
"licenses": [ | ||
{ | ||
"type": "MIT", | ||
"url": "http://github.com/icetee/node-ftp/raw/master/LICENSE" | ||
} | ||
], | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "http://github.com/icetee/node-ftp.git" | ||
}, | ||
"devDependencies": { | ||
"babel-register": "^6.26.0", | ||
"chai": "^4.1.2", | ||
"dotenv": "^4.0.0", | ||
"eslint": "^4.10.0", | ||
"eslint-config-airbnb-base": "^12.1.0", | ||
"eslint-plugin-import": "^2.8.0", | ||
"mocha": "^5.1.1", | ||
"sinon": "^4.0.2", | ||
"sinon-chai": "^2.14.0" | ||
} | ||
} |
@@ -10,3 +10,3 @@ Description | ||
* [node.js](http://nodejs.org/) -- v0.8.0 or newer | ||
* [node.js](http://nodejs.org/) -- v6.4.0 or newer | ||
@@ -13,0 +13,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
68112
1
18
1761
1
9
6
+ Addedxregexp@3.2.0(transitive)
- Removedreadable-stream@1.1.x
- Removedcore-util-is@1.0.3(transitive)
- Removedinherits@2.0.4(transitive)
- Removedisarray@0.0.1(transitive)
- Removedreadable-stream@1.1.14(transitive)
- Removedstring_decoder@0.10.31(transitive)
- Removedxregexp@2.0.0(transitive)
Updatedxregexp@^3.2.0