jsftpd
Advanced tools
Comparing version 1.1.1 to 1.2.0
@@ -23,7 +23,4 @@ # Welcome to jsftpd documentation! | ||
const server = new ftpd({cnf: {username: 'john', password: 'doe'}) | ||
const server = new ftpd({cnf: {username: 'john', password: 'doe', basefolder: '/tmp'}) | ||
server.on('log', console.log) | ||
server.on('error', console.error) | ||
server.start() | ||
@@ -30,0 +27,0 @@ ``` |
# Configuration | ||
jsftpd takes an object when a new instance is created. This object can contain two properties: | ||
jsftpd takes an object when a new instance is created. This object can have different properties: | ||
```{code-block} javascript | ||
new ftpd({tls: {...}, cnf: {...}}) | ||
new ftpd({tls: {...}, cnf: {...}, hdl: {...}}) | ||
``` | ||
@@ -11,2 +11,3 @@ | ||
* **cnf**: jsftpd specific configuration items | ||
* **hdl**: jsftpd handler for specific FTP commands | ||
@@ -157,2 +158,13 @@ ## tls | ||
### allowUserFileRetrieve | ||
**Type: `boolean`**\ | ||
**Default: `true`** | ||
Allow to retrieve files. This only affects the main user. | ||
```{code-block} javascript | ||
new ftpd({cnf: {allowUserFileRetrieve: true}}) | ||
``` | ||
### allowUserFileOverwrite | ||
@@ -224,2 +236,13 @@ | ||
### allowAnonymousFileRetrieve | ||
**Type: `boolean`**\ | ||
**Default: `false`** | ||
Allow the anonymous user to retrieve files. | ||
```{code-block} javascript | ||
new ftpd({cnf: {allowAnonymousFileRetrieve: true}}) | ||
``` | ||
### allowAnonymousFileOverwrite | ||
@@ -268,1 +291,97 @@ | ||
``` | ||
## hdl | ||
jsftpd can use handler functions instead of reading/writing to the file system for several FTP commands. | ||
```{code-block} javascript | ||
const handler = { | ||
upload: async function (...) {}, | ||
download: async function (...) {}, | ||
list: async function (...) {}, | ||
rename: async function (...) {} | ||
} | ||
const server = new ftpd({hdl: handler}) | ||
``` | ||
The following FTP commands are covered by the handlers | ||
* **STOR**: Uploading files to the FTP server | ||
* **RETR**: Downloading files from the FTP server | ||
* **LIST**: Listing directory contents on the FTP server | ||
* **MLSD**: Listing directory contents on the FTP server | ||
* **RNFR**: Renaming files on the FTP server | ||
* **RNTO**: Renaming files on the FTP server | ||
### upload | ||
**Name: `upload`**\ | ||
**Returns: `boolean`** | ||
The upload function takes 5 arguments when being called from jsftpd. It must return `true` on success or `false` on any error handling the file upload. | ||
**username: `string` the user who has uploaded the file**\ | ||
**path: `string` relative path on the FTP server where the file is stored**\ | ||
**fileName: `string` the name of the file that is stored**\ | ||
**data: `Buffer` the file content that is stored**\ | ||
**offset: `number` the offset of the data received** | ||
```{code-block} javascript | ||
async upload (username, path, fileName, data, offset) { | ||
... | ||
} | ||
``` | ||
### download | ||
**Name: `upload`**\ | ||
**Returns: `Buffer`** | ||
The download function takes 4 arguments when being called from jsftpd. It must return the file content as a `Buffer`. | ||
**username: `string` the user who has uploaded the file**\ | ||
**path: `string` relative path on the FTP server where the file is stored**\ | ||
**fileName: `string` the name of the file that is stored**\ | ||
**data: `Buffer` the file content that is stored**\ | ||
**offset: `number` the offset of the data received** | ||
```{code-block} javascript | ||
async upload (username, path, fileName, data, offset) { | ||
... | ||
} | ||
``` | ||
### list | ||
**Name: `list`**\ | ||
**Returns: `string`** | ||
The list function takes 3 arguments when being called from jsftpd. It must return the content of the specified directory as a `string`. | ||
**username: `string` the current user**\ | ||
**path: `string` relative path on the FTP server**\ | ||
**format: `string` the format of the list reply (MLSD | LIST)** | ||
```{code-block} javascript | ||
async upload (username, path, format) { | ||
... | ||
} | ||
``` | ||
### rename | ||
**Name: `rename`**\ | ||
**Returns: `boolean`** | ||
The rename function takes 4 arguments when being called from jsftpd. It must return `true` on success or `false` on any error handling the file rename. | ||
**username: `string` the current user**\ | ||
**path: `string` relative path on the FTP server**\ | ||
**fromName: `string` the current file that needs to be renamed** | ||
**newName: `string` the new name of the file** | ||
```{code-block} javascript | ||
async upload (username, path, fromName, newName) { | ||
... | ||
} | ||
``` |
@@ -32,2 +32,3 @@ /* | ||
allowAnonymousFileCreate: false, | ||
allowAnonymousFileRetrieve: false, | ||
allowAnonymousFileOverwrite: false, | ||
@@ -38,10 +39,16 @@ allowAnonymousFileDelete: false, | ||
allowAnonymousLogin: false, | ||
minDataPort: 1024, | ||
uploadHandler: null, | ||
downloadHandler: null | ||
minDataPort: 1024 | ||
} | ||
const HandlerDefaults = { | ||
upload: async function () {}, | ||
download: async function () {}, | ||
list: async function () {}, | ||
rename: async function () {} | ||
} | ||
const UserDefaults = { | ||
allowLoginWithoutPassword: false, | ||
allowUserFileCreate: true, | ||
allowUserFileRetrieve: true, | ||
allowUserFileOverwrite: true, | ||
@@ -71,6 +78,8 @@ allowUserFileDelete: true, | ||
this._opt.cnf = Object.assign({}, FTPdefaults, UserDefaults, options && options.cnf) | ||
this._useTLS = options && options.tls | ||
this._opt.hdl = Object.assign({}, HandlerDefaults, options && options.hdl) | ||
this._useTLS = options && Object.keys(options).indexOf('tls') > -1 | ||
this._useHdl = options && Object.keys(options).indexOf('hdl') > -1 | ||
// checks | ||
if (!this._opt.cnf.uploadHandler && !fs.existsSync(this._opt.cnf.basefolder)) { | ||
if (!this._useHdl && !fs.existsSync(this._opt.cnf.basefolder)) { | ||
if (this._opt.cnf.basefolder === defaultBaseFolder) { | ||
@@ -92,3 +101,3 @@ fs.mkdirSync(defaultBaseFolder) | ||
this.DebugHandler(`FTP server listening on ${util.inspect(this._tcp.address(), { showHidden: false, depth: null, breakLength: 'Infinity' })}`) | ||
this.emit('listen-tcp', this._tcp.address()) | ||
this.emit('listen', { protocol: 'tcp', address: this._tcp.address().address.replace(/::ffff:/g, ''), port: this._tcp.address().port }) | ||
}) | ||
@@ -103,3 +112,3 @@ this._tcp.maxConnections = this._opt.cnf.maxConnections | ||
this.DebugHandler(`FTP server listening on ${util.inspect(this._tls.address(), { showHidden: false, depth: null, breakLength: 'Infinity' })}`) | ||
this.emit('listen-tls', this._tls.address()) | ||
this.emit('listen', { protocol: 'tls', address: this._tls.address().address.replace(/::ffff:/g, ''), port: this._tls.address().port }) | ||
}) | ||
@@ -141,4 +150,5 @@ this._tls.maxConnections = this._opt.cnf.maxConnections | ||
Handler (main, socket) { | ||
const connectionInfo = `[${socket.remoteAddress.replace(/::ffff:/g, '')}] [${socket.remotePort}]` | ||
const remoteAddr = socket.remoteAddress.replace(/::ffff:/g, '') | ||
const localAddr = socket.localAddress.replace(/::ffff:/g, '') | ||
const connectionInfo = `[${remoteAddr}] [${socket.remotePort}]` | ||
const socketKey = ++main.lastSocketKey | ||
@@ -164,2 +174,3 @@ main.openSockets[socketKey] = socket | ||
let allowFileCreate = false | ||
let allowFileRetrieve = false | ||
let allowFileOverwrite = false | ||
@@ -199,2 +210,3 @@ let allowFileDelete = false | ||
delete main.openSockets[socketKey] | ||
main._informLogoff(username, remoteAddr) | ||
main.DebugHandler(`${connectionInfo} FTP connection closed`) | ||
@@ -215,11 +227,15 @@ if (ftpData) { | ||
case LoginType.None: | ||
return main._writeToSocket(socket, '530', ' ', 'Not logged in', connectionInfo, SocketStateAfterWrite.Open) | ||
main._writeToSocket(socket, '530', ' ', 'Not logged in', connectionInfo, SocketStateAfterWrite.Open) | ||
break | ||
case LoginType.Anonymous: | ||
case LoginType.Password: | ||
return main._writeToSocket(socket, '331', ' ', `Password required for ${username}`, connectionInfo, SocketStateAfterWrite.Open) | ||
main._writeToSocket(socket, '331', ' ', `Password required for ${username}`, connectionInfo, SocketStateAfterWrite.Open) | ||
break | ||
case LoginType.NoPassword: | ||
authenticated = true | ||
return main._writeToSocket(socket, '232', ' ', 'User logged in', connectionInfo, SocketStateAfterWrite.Open) | ||
main._writeToSocket(socket, '232', ' ', 'User logged in', connectionInfo, SocketStateAfterWrite.Open) | ||
main._informLogin(username, remoteAddr) | ||
break | ||
default: | ||
return main._writeToSocket(socket, '331', ' ', `Password required for ${username}`, connectionInfo, SocketStateAfterWrite.Open) | ||
main._writeToSocket(socket, '331', ' ', `Password required for ${username}`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
@@ -235,2 +251,3 @@ } | ||
main._writeToSocket(socket, '230', ' ', 'Logged on', connectionInfo, SocketStateAfterWrite.Open) | ||
main._informLogin(username, remoteAddr) | ||
} else { | ||
@@ -338,36 +355,12 @@ main._writeToSocket(socket, '530', ' ', 'Username or password incorrect', connectionInfo, SocketStateAfterWrite.Open) | ||
let newPath = arg | ||
if (newPath.charAt(0) === '/') { | ||
let folder = path.join(basefolder, newPath) | ||
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true && main._beginsWith(basefolder, folder) === true) { | ||
if (folder.charAt(folder.length - 1) !== '/') { | ||
folder += '/' | ||
} | ||
if (newPath.charAt(newPath.length - 1) !== '/') { | ||
newPath += '/' | ||
} | ||
absolutePath = folder | ||
relativePath = newPath | ||
return main._writeToSocket(socket, '250', ' ', `CWD successful. "${relativePath}" is current directory`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
} else if (newPath !== '..') { | ||
let folder = path.join(basefolder, relativePath, newPath) | ||
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true && main._beginsWith(basefolder, folder) === true) { | ||
if (folder.charAt(folder.length - 1) !== '/') { | ||
folder += '/' | ||
} | ||
if (newPath.charAt(newPath.length - 1) !== '/') { | ||
newPath += '/' | ||
} | ||
absolutePath = folder | ||
relativePath += newPath | ||
return main._writeToSocket(socket, '250', ' ', `CWD successful. "${relativePath}" is current directory`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
} else if (newPath === '..') { | ||
if (relativePath !== '/') { | ||
newPath = relativePath.split('/') | ||
newPath.pop() | ||
newPath.pop() | ||
newPath = newPath.join('/') + '/' | ||
const folder = path.join(basefolder, newPath) | ||
if (main._useHdl === false) { | ||
if (newPath.charAt(0) === '/') { | ||
let folder = path.join(basefolder, newPath) | ||
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true && main._beginsWith(basefolder, folder) === true) { | ||
if (folder.charAt(folder.length - 1) !== '/') { | ||
folder += '/' | ||
} | ||
if (newPath.charAt(newPath.length - 1) !== '/') { | ||
newPath += '/' | ||
} | ||
absolutePath = folder | ||
@@ -377,4 +370,30 @@ relativePath = newPath | ||
} | ||
} else { | ||
return main._writeToSocket(socket, '250', ' ', `CWD successful. "${relativePath}" is current directory`, connectionInfo, SocketStateAfterWrite.Open) | ||
} else if (newPath !== '..') { | ||
let folder = path.join(basefolder, relativePath, newPath) | ||
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true && main._beginsWith(basefolder, folder) === true) { | ||
if (folder.charAt(folder.length - 1) !== '/') { | ||
folder += '/' | ||
} | ||
if (newPath.charAt(newPath.length - 1) !== '/') { | ||
newPath += '/' | ||
} | ||
absolutePath = folder | ||
relativePath += newPath | ||
return main._writeToSocket(socket, '250', ' ', `CWD successful. "${relativePath}" is current directory`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
} else if (newPath === '..') { | ||
if (relativePath !== '/') { | ||
newPath = relativePath.split('/') | ||
newPath.pop() | ||
newPath.pop() | ||
newPath = newPath.join('/') + '/' | ||
const folder = path.join(basefolder, newPath) | ||
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true && main._beginsWith(basefolder, folder) === true) { | ||
absolutePath = folder | ||
relativePath = newPath | ||
return main._writeToSocket(socket, '250', ' ', `CWD successful. "${relativePath}" is current directory`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
} else { | ||
return main._writeToSocket(socket, '250', ' ', `CWD successful. "${relativePath}" is current directory`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
} | ||
@@ -395,3 +414,3 @@ } | ||
} | ||
if (fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) { | ||
if (main._useHdl === false && fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) { | ||
main._writeToSocket(socket, '213', ' ', `${fs.statSync(file).size.toString()}`, connectionInfo, SocketStateAfterWrite.Open) | ||
@@ -413,3 +432,3 @@ } else { | ||
} | ||
if (fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) { | ||
if (main._useHdl === false && fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) { | ||
if (allowFileDelete) { | ||
@@ -437,3 +456,3 @@ fs.unlinkSync(file) | ||
} | ||
if (allowFolderDelete && main._beginsWith(basefolder, folder) === true) { | ||
if (main._useHdl === false && allowFolderDelete && main._beginsWith(basefolder, folder) === true) { | ||
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true) { | ||
@@ -460,3 +479,3 @@ fs.rmSync(folder, { force: true, recursive: true }) | ||
} | ||
if (allowFolderCreate && main._beginsWith(basefolder, folder) === true) { | ||
if (main._useHdl === false && allowFolderCreate && main._beginsWith(basefolder, folder) === true) { | ||
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true) { | ||
@@ -478,30 +497,35 @@ main._writeToSocket(socket, '550', ' ', 'Folder exists', connectionInfo, SocketStateAfterWrite.Open) | ||
const LIST = function (cmd, arg) { | ||
dataObj.method = function (obj) { | ||
if (obj.dataSocket && obj.cmdSocket && obj.absolutePath) { | ||
dataObj.method = async function (obj) { | ||
if (obj.dataSocket && obj.cmdSocket && obj.absolutePath && obj.relativePath) { | ||
if (asciiOn) { | ||
obj.dataSocket.setEncoding('ascii') | ||
} | ||
const read = fs.readdirSync(obj.absolutePath) | ||
let listData = '' | ||
for (let i = 0; i < read.length; i++) { | ||
let line | ||
const file = path.join(obj.absolutePath, read[i].trim()) | ||
const stat = fs.statSync(file) | ||
if (obj.MLSD === true) { | ||
const size = (fs.statSync(file).isDirectory() === true) ? '' : 'size=' + stat.size.toString() + ';' | ||
line = util.format('type=%s;modify=%s;%s %s\r\n', (stat.isDirectory() === true) ? 'dir' : 'file' | ||
, main._getDateForMLSD(stat.mtime) | ||
, size | ||
, read[i].trim()) | ||
} else { | ||
let size = (fs.statSync(file).isDirectory() === true) ? '0' : stat.size.toString() | ||
size = new Array(14 - size.length).join(' ') + size | ||
line = util.format('%s 1 %s %s %s %s %s\r\n', (stat.isDirectory() === true) ? 'dr--r--r--' : '-r--r--r--' | ||
, username | ||
, username | ||
, size | ||
, main._getDateForLIST(stat.mtime) | ||
, read[i].trim()) | ||
if (main._useHdl === true) { | ||
const data = await main._opt.hdl.list(username, obj.relativePath, obj.MLSD) | ||
data && (listData = data) | ||
} else { | ||
const read = fs.readdirSync(obj.absolutePath) | ||
for (let i = 0; i < read.length; i++) { | ||
let line | ||
const file = path.join(obj.absolutePath, read[i].trim()) | ||
const stat = fs.statSync(file) | ||
if (obj.MLSD === true) { | ||
const size = (fs.statSync(file).isDirectory() === true) ? '' : 'size=' + stat.size.toString() + ';' | ||
line = util.format('type=%s;modify=%s;%s %s\r\n', (stat.isDirectory() === true) ? 'dir' : 'file' | ||
, main._getDateForMLSD(stat.mtime) | ||
, size | ||
, read[i].trim()) | ||
} else { | ||
let size = (fs.statSync(file).isDirectory() === true) ? '0' : stat.size.toString() | ||
size = new Array(14 - size.length).join(' ') + size | ||
line = util.format('%s 1 %s %s %s %s %s\r\n', (stat.isDirectory() === true) ? 'dr--r--r--' : '-r--r--r--' | ||
, username | ||
, username | ||
, size | ||
, main._getDateForLIST(stat.mtime) | ||
, read[i].trim()) | ||
} | ||
listData += line | ||
} | ||
listData += line | ||
} | ||
@@ -512,3 +536,3 @@ if (listData.length === 0) { | ||
main.DebugHandler(`${connectionInfo} LIST response on data channel\r\n${listData}`) | ||
obj.dataSocket.end(listData) | ||
obj.dataSocket.end(Buffer.from(listData)) | ||
main._writeToSocket(obj.cmdSocket, '226', ' ', `Successfully transferred "${relativePath}"`, connectionInfo, SocketStateAfterWrite.Open) | ||
@@ -520,2 +544,3 @@ } | ||
dataObj.absolutePath = absolutePath | ||
dataObj.relativePath = relativePath | ||
openDataChannel(dataObj) | ||
@@ -549,11 +574,15 @@ } | ||
main._getDataPort((port) => { | ||
ftpData.listen(port, () => { | ||
main.DebugHandler(`${connectionInfo} listening on ${ftpData.address().port} for data connection`) | ||
dataObj = {} | ||
pasv = true | ||
const p1 = (ftpData.address().port) / 256 | 0 | ||
const p2 = (ftpData.address().port) % 256 | ||
const pasvData = util.format('Entering passive mode (%s,%d,%d)', localAddr.split('.').join(','), p1, p2) | ||
main._writeToSocket(socket, '227', ' ', `${pasvData}`, connectionInfo, SocketStateAfterWrite.Open) | ||
}) | ||
if (port > 0) { | ||
ftpData.listen(port, () => { | ||
main.DebugHandler(`${connectionInfo} listening on ${ftpData.address().port} for data connection`) | ||
dataObj = {} | ||
pasv = true | ||
const p1 = (ftpData.address().port) / 256 | 0 | ||
const p2 = (ftpData.address().port) % 256 | ||
const pasvData = util.format('Entering passive mode (%s,%d,%d)', localAddr.split('.').join(','), p1, p2) | ||
main._writeToSocket(socket, '227', ' ', `${pasvData}`, connectionInfo, SocketStateAfterWrite.Open) | ||
}) | ||
} else { | ||
main._writeToSocket(socket, '501', ' ', 'Passive command failed', connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
}) | ||
@@ -587,9 +616,13 @@ } | ||
main._getDataPort((port) => { | ||
ftpData.listen(port, () => { | ||
main.DebugHandler(`${connectionInfo} listening on ${ftpData.address().port} for data connection`) | ||
dataObj = {} | ||
pasv = true | ||
const pasvData = util.format('Entering extended passive mode (|||%d|)', ftpData.address().port) | ||
main._writeToSocket(socket, '229', ' ', `${pasvData}`, connectionInfo, SocketStateAfterWrite.Open) | ||
}) | ||
if (port > 0) { | ||
ftpData.listen(port, () => { | ||
main.DebugHandler(`${connectionInfo} listening on ${ftpData.address().port} for data connection`) | ||
dataObj = {} | ||
pasv = true | ||
const pasvData = util.format('Entering extended passive mode (|||%d|)', ftpData.address().port) | ||
main._writeToSocket(socket, '229', ' ', `${pasvData}`, connectionInfo, SocketStateAfterWrite.Open) | ||
}) | ||
} else { | ||
main._writeToSocket(socket, '501', ' ', 'Extended passive command failed', connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
}) | ||
@@ -609,12 +642,22 @@ } | ||
} | ||
if (fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) { | ||
if (allowFileRetrieve === false) { | ||
return main._writeToSocket(socket, '550', ' ', `Transfer failed "${relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
if (main._useHdl === false && fs.existsSync(file) === false) { | ||
main._writeToSocket(socket, '550', ' ', 'File not found', connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
if (((fs.existsSync(file) === true && fs.statSync(file).isFile() === true) || main._useHdl) && main._beginsWith(basefolder, file) === true) { | ||
dataObj.method = async function (obj) { | ||
if (obj.dataSocket && obj.cmdSocket && obj.file && obj.relativeFile) { | ||
asciiOn && obj.dataSocket.setEncoding('ascii') | ||
if (obj.handler) { | ||
const data = await obj.handler(username, relativePath, obj.fileName, retrOffset) | ||
if (main._useHdl) { | ||
const data = await main._opt.hdl.download(username, relativePath, obj.fileName, retrOffset) | ||
retrOffset = 0 | ||
obj.dataSocket.write(data) | ||
obj.dataSocket.end() | ||
main._writeToSocket(obj.cmdSocket, '226', ' ', `Successfully transferred "${obj.relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open) | ||
if (Buffer.isBuffer(data)) { | ||
obj.dataSocket.end(data) | ||
main._writeToSocket(obj.cmdSocket, '226', ' ', `Successfully transferred "${obj.relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open) | ||
} else { | ||
obj.dataSocket.end() | ||
main._writeToSocket(obj.cmdSocket, '550', ' ', `Transfer failed "${relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
} else { | ||
@@ -651,6 +694,3 @@ const streamOpts = { | ||
dataObj.relativeFile = relativeFile | ||
dataObj.handler = main._opt.cnf.downloadHandler | ||
openDataChannel(dataObj) | ||
} else { | ||
main._writeToSocket(socket, '550', ' ', 'File not found', connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
@@ -695,3 +735,2 @@ } | ||
dataObj.relativeFile = relativeFile | ||
dataObj.handler = main._opt.cnf.uploadHandler | ||
dataObj.method = function (obj) { | ||
@@ -702,8 +741,12 @@ if (obj.dataSocket && obj.cmdSocket && obj.relativeFile) { | ||
} | ||
if (obj.handler) { | ||
if (main._useHdl) { | ||
const data = [] | ||
obj.dataSocket.on('data', (d) => data.push(d)) | ||
obj.dataSocket.on('close', async () => { | ||
await obj.handler(username, relativePath, obj.fileName, Buffer.concat(data), obj.retrOffset) | ||
main._writeToSocket(obj.cmdSocket, '226', ' ', `Successfully transferred "${obj.relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open) | ||
const success = await main._opt.hdl.upload(username, relativePath, obj.fileName, Buffer.concat(data), obj.retrOffset) | ||
if (success === true) { | ||
main._writeToSocket(obj.cmdSocket, '226', ' ', `Successfully transferred "${obj.relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open) | ||
} else { | ||
main._writeToSocket(obj.cmdSocket, '550', ' ', `Transfer failed "${relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
}) | ||
@@ -719,3 +762,3 @@ } else if (obj.stream) { | ||
} | ||
if (dataObj.handler) { | ||
if (main._useHdl) { | ||
dataObj.retrOffset = retrOffset | ||
@@ -782,3 +825,3 @@ retrOffset = 0 | ||
} | ||
if (fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) { | ||
if (((fs.existsSync(file) === true && fs.statSync(file).isFile() === true) || main._useHdl === true) && main._beginsWith(basefolder, file) === true) { | ||
renameFrom = file | ||
@@ -794,3 +837,3 @@ main._writeToSocket(socket, '350', ' ', 'File exists', connectionInfo, SocketStateAfterWrite.Open) | ||
*/ | ||
const RNTO = function (cmd, arg) { | ||
const RNTO = async function (cmd, arg) { | ||
const relativeFile = arg | ||
@@ -803,6 +846,14 @@ let file | ||
} | ||
if (fs.existsSync(file) === false && main._beginsWith(basefolder, file) === true) { | ||
if (main._useHdl === false && fs.existsSync(file) === false && main._beginsWith(basefolder, file) === true) { | ||
fs.renameSync(renameFrom, file) | ||
renameFrom = '' | ||
main._writeToSocket(socket, '250', ' ', 'File renamed successfully', connectionInfo, SocketStateAfterWrite.Open) | ||
} else if (main._useHdl === true) { | ||
const success = await main._opt.hdl.rename(username, relativePath, path.basename(renameFrom), relativeFile) | ||
renameFrom = '' | ||
if (success === true) { | ||
main._writeToSocket(socket, '250', ' ', 'File renamed successfully', connectionInfo, SocketStateAfterWrite.Open) | ||
} else { | ||
main._writeToSocket(socket, '550', ' ', 'File rename failed', connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
} else { | ||
@@ -824,6 +875,8 @@ main._writeToSocket(socket, '550', ' ', 'File already exists', connectionInfo, SocketStateAfterWrite.Open) | ||
} | ||
if (fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) { | ||
if (main._useHdl === false && fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) { | ||
const mtime = main._getDateForMFMT(time) | ||
fs.utimesSync(file, mtime, mtime) | ||
main._writeToSocket(socket, '253', ' ', 'Date/time changed okay', connectionInfo, SocketStateAfterWrite.Open) | ||
} else if (main._useHdl === true) { | ||
main._writeToSocket(socket, '253', ' ', 'Date/time changed okay', connectionInfo, SocketStateAfterWrite.Open) | ||
} else { | ||
@@ -897,2 +950,3 @@ main._writeToSocket(socket, '550', ' ', 'File does not exist', connectionInfo, SocketStateAfterWrite.Open) | ||
allowFileCreate = main._opt.cnf.allowAnonymousFileCreate | ||
allowFileRetrieve = main._opt.cnf.allowAnonymousFileRetrieve | ||
allowFileOverwrite = main._opt.cnf.allowAnonymousFileOverwrite | ||
@@ -926,2 +980,3 @@ allowFileDelete = main._opt.cnf.allowAnonymousFileDelete | ||
Object.prototype.hasOwnProperty.call(obj, 'allowUserFileCreate') && (allowFileCreate = obj.allowUserFileCreate) | ||
Object.prototype.hasOwnProperty.call(obj, 'allowUserFileRetrieve') && (allowFileRetrieve = obj.allowUserFileRetrieve) | ||
Object.prototype.hasOwnProperty.call(obj, 'allowUserFileOverwrite') && (allowFileOverwrite = obj.allowUserFileOverwrite) | ||
@@ -969,3 +1024,3 @@ Object.prototype.hasOwnProperty.call(obj, 'allowUserFileDelete') && (allowFileDelete = obj.allowUserFileDelete) | ||
dataChannel.on('error', main.ErrorHandler) | ||
dataChannel.maxConnections = main._opt.cnf.maxConnections | ||
dataChannel.maxConnections = 1 | ||
return dataChannel | ||
@@ -1002,2 +1057,10 @@ } | ||
_informLogin (username, remoteAddr) { | ||
this.emit('login', { user: username, address: remoteAddr, total: Object.keys(this.openSockets).length }) | ||
} | ||
_informLogoff (username, remoteAddr) { | ||
this.emit('logoff', { user: username, address: remoteAddr, total: Object.keys(this.openSockets).length }) | ||
} | ||
_writeToSocket (socket, code, delimiter, message, connectionInfo, socketState) { | ||
@@ -1014,7 +1077,8 @@ this.LogHandler(`${connectionInfo} > ${code}${delimiter}${message}`) | ||
_getDataPort (resolve) { | ||
if (this._opt.cnf.minDataPort > 0 && this._opt.cnf.minDataPort < 65535) { | ||
const config = this._opt.cnf | ||
if (config.minDataPort > 0 && config.minDataPort < 65535) { | ||
const testPort = function (port) { | ||
const server = net.createServer() | ||
server.once('error', function (_err) { | ||
if (port > (this._opt.cnf.minDataPort + this._opt.cnf.maxConnections)) { | ||
if (port >= (config.minDataPort + config.maxConnections)) { | ||
resolve(0) | ||
@@ -1033,3 +1097,3 @@ } else { | ||
} | ||
testPort(this._opt.cnf.minDataPort) | ||
testPort(config.minDataPort) | ||
} else { | ||
@@ -1036,0 +1100,0 @@ resolve(0) |
{ | ||
"name": "jsftpd", | ||
"id": "jsftpd", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"description": "FTP server for node.js", | ||
@@ -43,3 +43,3 @@ "main": "index.js", | ||
"install-dev": "npm install --save-dev && husky install", | ||
"test": "jest" | ||
"test": "jest --runInBand" | ||
}, | ||
@@ -46,0 +46,0 @@ "license": "MIT", |
@@ -27,7 +27,4 @@ # jsftpd | ||
const server = new ftpd({cnf: {username: 'john', password: 'doe'}) | ||
const server = new ftpd({cnf: {username: 'john', password: 'doe', basefolder: '/tmp'}) | ||
server.on('log', console.log) | ||
server.on('error', console.error) | ||
server.start() | ||
@@ -42,2 +39,3 @@ ``` | ||
- `tls` property object. Takes any configuration option as per node.js tls.createServer [options](https://nodejs.org/api/tls.html#tlscreateserveroptions-secureconnectionlistener) | ||
- `cnf` property object. Takes jsftpd specific configuration items. See full documentation [here](https://jsftpd.readthedocs.io/en/latest/) | ||
- `cnf` property object. Takes jsftpd specific configuration items. See full documentation [here](https://jsftpd.readthedocs.io/en/latest/options.html#cnf) | ||
- `hdl` property object. Takes handler functions for specific FTP commands. See full documentation [here](https://jsftpd.readthedocs.io/en/latest/options.html#hdl) |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
186562
26
3520
40
17