Socket
Socket
Sign inDemoInstall

jsftpd

Package Overview
Dependencies
0
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.0 to 1.1.0

docs/index.md

145

lib/jsftpd.js

@@ -31,6 +31,3 @@ /*

user: [],
allowUserFileOverwrite: true,
allowUserFileDelete: true,
allowUserFolderDelete: true,
allowUserFolderCreate: true,
allowAnonymousFileCreate: false,
allowAnonymousFileOverwrite: false,

@@ -40,8 +37,17 @@ allowAnonymousFileDelete: false,

allowAnonymousFolderCreate: false,
allowLoginWithoutPassword: false,
allowAnonymousLogin: false,
minDataPort: 1024,
uploadHandler: null
uploadHandler: null,
downloadHandler: null
}
const UserDefaults = {
allowLoginWithoutPassword: false,
allowUserFileCreate: true,
allowUserFileOverwrite: true,
allowUserFileDelete: true,
allowUserFolderDelete: true,
allowUserFolderCreate: true
}
const LoginType = Object.freeze({

@@ -64,3 +70,3 @@ None: 0,

this._opt.tls = Object.assign({}, TLSserverDefaults, options && options.tls)
this._opt.cnf = Object.assign({}, FTPdefaults, options && options.cnf)
this._opt.cnf = Object.assign({}, FTPdefaults, UserDefaults, options && options.cnf)
this._useTLS = options && options.tls

@@ -126,3 +132,3 @@

let authenticated = false
let isSecure = socket.encrypted
let isSecure = socket.encrypted || false
let protection = false

@@ -143,2 +149,3 @@ let username

let retrOffset = 0
let allowFileCreate = false
let allowFileOverwrite = false

@@ -314,3 +321,3 @@ let allowFileDelete = false

if (newPath.charAt(0) === '/') {
let folder = path.normalize(basefolder + newPath)
let folder = path.join(basefolder, newPath)
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true && main._beginsWith(basefolder, folder) === true) {

@@ -328,3 +335,3 @@ if (folder.charAt(folder.length - 1) !== '/') {

} else if (newPath !== '..') {
let folder = path.normalize(basefolder + relativePath + newPath)
let folder = path.join(basefolder, relativePath, newPath)
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true && main._beginsWith(basefolder, folder) === true) {

@@ -341,18 +348,17 @@ if (folder.charAt(folder.length - 1) !== '/') {

}
} else if (relativePath !== '/') {
newPath = relativePath.split('/')
newPath.pop()
newPath.pop()
newPath = newPath.join('/') + '/'
let folder = path.normalize(basefolder + newPath)
if (fs.existsSync(folder) === true && fs.statSync(folder).isDirectory() === true && main._beginsWith(basefolder, folder) === true) {
if (folder.charAt(folder.length - 1) !== '/') {
folder += '/'
} 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)
}
absolutePath = folder
relativePath = newPath
} else {
return main._writeToSocket(socket, '250', ' ', `CWD successful. "${relativePath}" is current directory`, connectionInfo, SocketStateAfterWrite.Open)
}
} else if (relativePath === '/' && newPath === '..') {
return main._writeToSocket(socket, '250', ' ', `CWD successful. "${relativePath}" is current directory`, connectionInfo, SocketStateAfterWrite.Open)
}

@@ -408,5 +414,5 @@ return main._writeToSocket(socket, '530', ' ', 'CWD not successful', connectionInfo, SocketStateAfterWrite.Open)

if (folder.charAt(0) === '/') {
folder = path.normalize(basefolder + folder)
folder = path.join(basefolder, folder)
} else {
folder = path.normalize(basefolder + relativePath + folder)
folder = path.join(basefolder, relativePath, folder)
}

@@ -431,5 +437,5 @@ if (allowFolderDelete && main._beginsWith(basefolder, folder) === true) {

if (folder.charAt(0) === '/') {
folder = path.normalize(basefolder + folder)
folder = path.join(basefolder, folder)
} else {
folder = path.normalize(basefolder + relativePath + folder)
folder = path.join(basefolder, relativePath, folder)
}

@@ -575,33 +581,41 @@ if (allowFolderCreate && main._beginsWith(basefolder, folder) === true) {

if (relativeFile.charAt(0) === '/') {
file = path.normalize(basefolder + relativeFile)
file = path.join(basefolder, relativeFile)
} else {
file = path.normalize(basefolder + relativePath + relativeFile)
file = path.join(basefolder, relativePath, relativeFile)
}
if (fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) {
dataObj.method = function (obj) {
dataObj.method = async function (obj) {
if (obj.dataSocket && obj.cmdSocket && obj.file && obj.relativeFile) {
const streamOpts = {
flags: 'r',
start: retrOffset,
encoding: asciiOn ? 'ascii' : null,
autoClose: true,
emitClose: true
}
retrOffset = 0
asciiOn && obj.dataSocket.setEncoding('ascii')
const stream = fs.createReadStream(obj.file, streamOpts)
stream.on('error', main.ErrorHandler)
stream.on('open', () => {
obj.dataSocket.on('close', () => {
if (!obj.dataSocket.destroyed) {
stream.destroy()
main._writeToSocket(obj.cmdSocket, '426', ' ', `Connection closed. Aborted transfer of "${obj.relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open)
}
})
stream.pipe(obj.dataSocket)
})
stream.on('end', () => {
if (obj.handler) {
const data = await obj.handler(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)
})
} else {
const streamOpts = {
flags: 'r',
start: retrOffset,
encoding: asciiOn ? 'ascii' : null,
autoClose: true,
emitClose: true
}
retrOffset = 0
const stream = fs.createReadStream(obj.file, streamOpts)
stream.on('error', main.ErrorHandler)
stream.on('open', () => {
obj.dataSocket.on('close', () => {
if (!obj.dataSocket.destroyed) {
stream.destroy()
main._writeToSocket(obj.cmdSocket, '426', ' ', `Connection closed. Aborted transfer of "${obj.relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open)
}
})
stream.pipe(obj.dataSocket)
})
stream.on('end', () => {
obj.dataSocket.end()
main._writeToSocket(obj.cmdSocket, '226', ' ', `Successfully transferred "${obj.relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open)
})
}
}

@@ -611,3 +625,5 @@ }

dataObj.file = file
dataObj.fileName = path.basename(file)
dataObj.relativeFile = relativeFile
dataObj.handler = main._opt.cnf.downloadHandler
openDataChannel(dataObj)

@@ -640,5 +656,5 @@ } else {

if (relativeFile.charAt(0) === '/') {
file = path.normalize(basefolder + relativeFile)
file = path.join(basefolder, relativeFile)
} else {
file = path.normalize(basefolder + relativePath + relativeFile)
file = path.join(basefolder, relativePath, relativeFile)
}

@@ -648,2 +664,5 @@ if (fs.existsSync(file) === true && allowFileOverwrite === false) {

}
if (fs.existsSync(file) === false && allowFileCreate === false) {
return main._writeToSocket(socket, '550', ' ', `Transfer failed "${relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open)
}
if (main._beginsWith(basefolder, file) === true) {

@@ -663,4 +682,4 @@ dataObj.cmdSocket = socket

obj.dataSocket.on('data', (d) => data.push(d))
obj.dataSocket.on('close', () => {
obj.handler(relativePath, obj.fileName, Buffer.concat(data))
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)

@@ -678,2 +697,4 @@ })

if (dataObj.handler) {
dataObj.retrOffset = retrOffset
retrOffset = 0
openDataChannel(dataObj)

@@ -703,3 +724,3 @@ } else {

} else {
main._writeToSocket(socket, '550', ' ', `Transfer failed "${relativeFile}`, connectionInfo, SocketStateAfterWrite.Open)
main._writeToSocket(socket, '550', ' ', `Transfer failed "${relativeFile}"`, connectionInfo, SocketStateAfterWrite.Open)
}

@@ -774,5 +795,5 @@ }

if (file.charAt(0) === '/') {
file = path.normalize(basefolder + file)
file = path.join(basefolder, file)
} else {
file = path.normalize(basefolder + relativePath + file)
file = path.join(basefolder, relativePath, file)
}

@@ -825,3 +846,3 @@ if (fs.existsSync(file) === true && fs.statSync(file).isFile() === true && main._beginsWith(basefolder, file) === true) {

for (let i = 0; i < main._opt.cnf.user.length; i++) {
const u = main._opt.cnf.user[i]
const u = Object.assign({}, UserDefaults, main._opt.cnf.user[i])
if (typeof u === 'object' && username === u.username) {

@@ -851,2 +872,3 @@ if (Object.prototype.hasOwnProperty.call(u, 'allowLoginWithoutPassword') && u.allowLoginWithoutPassword === true) {

if (username === 'anonymous' && main._opt.cnf.allowAnonymousLogin) {
allowFileCreate = main._opt.cnf.allowAnonymousFileCreate
allowFileOverwrite = main._opt.cnf.allowAnonymousFileOverwrite

@@ -859,3 +881,3 @@ allowFileDelete = main._opt.cnf.allowAnonymousFileDelete

for (let i = 0; i < main._opt.cnf.user.length; i++) {
const u = main._opt.cnf.user[i]
const u = Object.assign({}, UserDefaults, main._opt.cnf.user[i])
if (typeof u === 'object' && username === u.username && (u.allowLoginWithoutPassword === true || password === u.password)) {

@@ -880,2 +902,3 @@ setUserRights(u)

}
Object.prototype.hasOwnProperty.call(obj, 'allowUserFileCreate') && (allowFileCreate = obj.allowUserFileCreate)
Object.prototype.hasOwnProperty.call(obj, 'allowUserFileOverwrite') && (allowFileOverwrite = obj.allowUserFileOverwrite)

@@ -962,3 +985,3 @@ Object.prototype.hasOwnProperty.call(obj, 'allowUserFileDelete') && (allowFileDelete = obj.allowUserFileDelete)

_beginsWith (needle, haystack) {
return (haystack.substr(0, needle.length) === needle)
return haystack.startsWith(needle)
}

@@ -965,0 +988,0 @@

{
"name": "jsftpd",
"id": "jsftpd",
"version": "1.0.0",
"description": "FTP server in pure javascript",
"version": "1.1.0",
"description": "FTP server for node.js",
"main": "index.js",

@@ -12,6 +12,10 @@ "repository": {

"keywords": [
"nodejs",
"ftp",
"ftpd",
"files",
"daemon"
"ftpd-server",
"file-transfer",
"ftp-server",
"file-transfer-protocol",
"ftpserver"
],

@@ -18,0 +22,0 @@ "author": {

const { ftpd } = require('../index')
const util = require('util')
const net = require('net')

@@ -8,6 +9,14 @@ const tls = require('tls')

const formatPort = (addr, port) => {
const p1 = (port) / 256 | 0
const p2 = (port) % 256
return util.format('%s,%d,%d', addr.split('.').join(','), p1, p2)
}
afterEach(() => {
server.stop()
server.cleanup()
server = null
if (server) {
server.stop()
server.cleanup()
server = null
}
});

@@ -43,2 +52,28 @@

test('ftp server fails when basefolder does not exist', () => {
try {
server = new ftpd({cnf: {basefolder: '/NOTEXISTING'}})
} catch(err) {
expect(err.message).toMatch('Basefolder must exist')
}
});
test('error message when not logged in', async () => {
server = new ftpd({cnf: {port: 50021}})
expect(server).toBeInstanceOf(ftpd);
server.start()
const promiseSocket = new PromiseSocket(new net.Socket())
const socket = promiseSocket.stream
let content
await socket.connect(50021, 'localhost')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('220 Welcome')
await promiseSocket.write('REST 0')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('530 Not logged in')
await promiseSocket.end()
});
test('login as anonymous not allowed by default', async () => {

@@ -677,2 +712,3 @@ server = new ftpd({cnf: {port: 50021}})

allowLoginWithoutPassword: true,
allowUserFolderCreate: false,
}

@@ -732,2 +768,6 @@ ]

await promiseSocket.write('RMD /pete')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('550 Folder not found')
await promiseSocket.write('RMD john')

@@ -745,3 +785,4 @@ content = await promiseSocket.read();

allowLoginWithoutPassword: true,
allowUserFolderCreate: true
allowUserFolderCreate: true,
allowUserFolderDelete: false,
}

@@ -929,2 +970,42 @@ ]

test('test STOR message without permission', async () => {
const users = [
{
username: 'john',
allowLoginWithoutPassword: true,
allowUserFileCreate: false,
}
]
server = new ftpd({cnf: {port: 50021, user: users}})
expect(server).toBeInstanceOf(ftpd);
server.start()
let content
let promiseSocket = new PromiseSocket(new net.Socket())
let socket = promiseSocket.stream
await socket.connect(50021, 'localhost')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('220 Welcome')
await promiseSocket.write('USER john')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('232 User logged in')
await promiseSocket.write('EPSV')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('229 Entering extended passive mode (|||1024|)')
let promiseDataSocket = new PromiseSocket(new net.Socket())
let dataSocket = promiseDataSocket.stream
await dataSocket.connect(1024, 'localhost')
await promiseSocket.write('STOR mytestfile')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('550 Transfer failed "mytestfile"')
await dataSocket.end()
await promiseSocket.end()
});
test('test STOR message', async () => {

@@ -953,2 +1034,6 @@ const users = [

await promiseSocket.write('STOR ../../mytestfile')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('550 Transfer failed "../../mytestfile"')
await promiseSocket.write('EPSV')

@@ -1004,6 +1089,8 @@ content = await promiseSocket.read();

]
const handler = (path, filename, data) => {
const handler = async (username, path, filename, data, offset) => {
expect(username).toMatch('john')
expect(filename).toMatch('mytestfile')
expect(path).toMatch('/')
expect(data.toString()).toMatch('SOMETESTCONTENT')
expect(offset).toBe(0)
}

@@ -1098,2 +1185,6 @@ server = new ftpd({cnf: {uploadHandler: handler, port: 50021, user: users}})

await promiseSocket.write('RETR /someotherfile')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('550 File not found')
await promiseSocket.write('RETR mytestfile')

@@ -1113,2 +1204,74 @@ content = await promiseSocket.read();

test('test RETR message with handler', async () => {
const users = [
{
username: 'john',
allowLoginWithoutPassword: true,
}
]
const handler = async (username, path, filename, offset) => {
expect(username).toMatch('john')
expect(filename).toMatch('mytestfile')
expect(path).toMatch('/')
expect(offset).toBe(0)
return 'SOMETESTCONTENT'
}
server = new ftpd({cnf: {downloadHandler: handler, port: 50021, user: users}})
expect(server).toBeInstanceOf(ftpd);
server.start()
let content
let promiseSocket = new PromiseSocket(new net.Socket())
let socket = promiseSocket.stream
await socket.connect(50021, 'localhost')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('220 Welcome')
await promiseSocket.write('USER john')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('232 User logged in')
await promiseSocket.write('EPSV')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('229 Entering extended passive mode (|||1024|)')
let promiseDataSocket = new PromiseSocket(new net.Socket())
let dataSocket = promiseDataSocket.stream
await dataSocket.connect(1024, 'localhost')
await promiseSocket.write('STOR mytestfile')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('150 Opening data channel')
await promiseDataSocket.write('SOMETESTCONTENT');
dataSocket.end()
await promiseDataSocket.end()
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('226 Successfully transferred "mytestfile"')
await promiseSocket.write('EPSV')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('229 Entering extended passive mode (|||1024|)')
promiseDataSocket = new PromiseSocket(new net.Socket())
dataSocket = promiseDataSocket.stream
await dataSocket.connect(1024, 'localhost')
await promiseSocket.write('RETR /someotherfile')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('550 File not found')
await promiseSocket.write('RETR mytestfile')
// content = await promiseSocket.read();
// expect(content.toString().trim()).toBe('150 Opening data channel')
content = await promiseDataSocket.read();
expect(content.toString().trim()).toMatch('SOMETESTCONTENT')
await promiseDataSocket.end()
await promiseSocket.end()
});
test('test MFMT message', async () => {

@@ -1264,2 +1427,3 @@ const users = [

allowLoginWithoutPassword: true,
allowUserFileDelete: false,
}

@@ -1352,2 +1516,6 @@ ]

await promiseSocket.write('DELE someotherfile')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('550 File not found')
await promiseSocket.write('DELE mytestfile')

@@ -1567,1 +1735,91 @@ content = await promiseSocket.read();

});
test('test PORT message', async () => {
const users = [
{
username: 'john',
allowLoginWithoutPassword: true,
allowUserFolderCreate: true
}
]
server = new ftpd({cnf: {port: 50021, user: users}})
expect(server).toBeInstanceOf(ftpd);
server.start()
let content
let promiseSocket = new PromiseSocket(new net.Socket())
let socket = promiseSocket.stream
await socket.connect(50021, 'localhost')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('220 Welcome')
await promiseSocket.write('USER john')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('232 User logged in')
await promiseSocket.write('MKD john')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('250 Folder created successfully')
const dataServer = net.createServer()
let promiseDataSocket = new PromiseSocket(dataServer)
await promiseDataSocket.stream.listen(20, '127.0.0.1')
const portData = formatPort('127.0.0.1', 20)
await promiseSocket.write(`PORT ${portData}`)
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('200 Port command successful')
await promiseSocket.write('MLSD')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('150 Opening data channel')
await promiseDataSocket.stream.close()
await promiseSocket.end()
});
test('test EPRT message', async () => {
const users = [
{
username: 'john',
allowLoginWithoutPassword: true,
allowUserFolderCreate: true
}
]
server = new ftpd({cnf: {port: 50021, user: users}})
expect(server).toBeInstanceOf(ftpd);
server.start()
let content
let promiseSocket = new PromiseSocket(new net.Socket())
let socket = promiseSocket.stream
await socket.connect(50021, 'localhost')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('220 Welcome')
await promiseSocket.write('USER john')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('232 User logged in')
await promiseSocket.write('MKD john')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('250 Folder created successfully')
const dataServer = net.createServer()
let promiseDataSocket = new PromiseSocket(dataServer)
await promiseDataSocket.stream.listen(20, '127.0.0.1')
await promiseSocket.write(`EPRT ||127.0.0.1|20|`)
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('200 Extended Port command successful')
await promiseSocket.write('MLSD')
content = await promiseSocket.read();
expect(content.toString().trim()).toBe('150 Opening data channel')
await promiseDataSocket.stream.close()
await promiseSocket.end()
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc