Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ad45fc757 | ||
|
|
bc8abb14da | ||
|
|
0b55b3f79d | ||
|
|
4f4a6c25a5 | ||
|
|
45a4bf15bf | ||
|
|
a1be4416a7 | ||
|
|
32a0750e2c | ||
|
|
4eb17015f1 | ||
|
|
d2566e7745 | ||
|
|
f8cd1e8f64 | ||
|
|
e0e676e7e9 | ||
|
|
a7775a46ae | ||
|
|
f9c81b162a | ||
|
|
e1f1aa09cd | ||
|
|
b0174bb24e |
@@ -1,7 +1,7 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
node: circleci/node@4.1.0
|
||||
node: circleci/node@5.0.2
|
||||
|
||||
commands:
|
||||
setup_git_bot:
|
||||
@@ -58,6 +58,32 @@ jobs:
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
release_scheduled:
|
||||
triggers:
|
||||
# 6:03 UTC (mornings) 1 monday
|
||||
- schedule:
|
||||
cron: "3 6 * * 1"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
jobs:
|
||||
- lint
|
||||
- node/test:
|
||||
matrix:
|
||||
parameters:
|
||||
version:
|
||||
- '12.22'
|
||||
- '14.19'
|
||||
- '16.14'
|
||||
- 'current'
|
||||
- release:
|
||||
context: npm-deploy-av
|
||||
requires:
|
||||
- node/test
|
||||
- lint
|
||||
|
||||
test:
|
||||
jobs:
|
||||
- lint
|
||||
@@ -65,14 +91,14 @@ workflows:
|
||||
matrix:
|
||||
parameters:
|
||||
version:
|
||||
- '10.23'
|
||||
- '12.20'
|
||||
- '14.15'
|
||||
- '12.22'
|
||||
- '14.19'
|
||||
- '16.14'
|
||||
- 'current'
|
||||
- release_dry_run:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
only: main
|
||||
requires:
|
||||
- node/test
|
||||
- lint
|
||||
@@ -81,6 +107,6 @@ workflows:
|
||||
requires:
|
||||
- release_dry_run
|
||||
- release:
|
||||
context: deploy-npm
|
||||
context: npm-deploy-av
|
||||
requires:
|
||||
- hold_release
|
||||
|
||||
66
README.md
66
README.md
@@ -36,16 +36,25 @@
|
||||
## Usage
|
||||
|
||||
```js
|
||||
// Quick start
|
||||
|
||||
// Quick start, create an active ftp server.
|
||||
const FtpSrv = require('ftp-srv');
|
||||
const ftpServer = new FtpSrv({ options ... });
|
||||
|
||||
ftpServer.on('login', (data, resolve, reject) => { ... });
|
||||
...
|
||||
const port=21;
|
||||
const ftpServer = new FtpSrv({
|
||||
url: "ftp://0.0.0.0:" + port,
|
||||
anonymous: true
|
||||
});
|
||||
|
||||
ftpServer.listen()
|
||||
.then(() => { ... });
|
||||
ftpServer.on('login', (data, resolve, reject) => {
|
||||
if(data.username === 'anonymous' && data.password === 'anonymous'){
|
||||
return resolve({ root:"/" });
|
||||
}
|
||||
return reject(new errors.GeneralError('Invalid username or password', 401));
|
||||
});
|
||||
|
||||
ftpServer.listen().then(() => {
|
||||
console.log('Ftp server is starting...')
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
@@ -65,23 +74,34 @@ __Default:__ `"ftp://127.0.0.1:21"`
|
||||
- A function which takes one parameter containing the remote IP address of the FTP client. This can be useful when the user wants to return a different IP address depending if the user is connecting from Internet or from an LAN address.
|
||||
Example:
|
||||
```js
|
||||
const {Netmask} = require('netmask');
|
||||
const networks = {
|
||||
'$GATEWAY_IP/32': `${public_ip}`,
|
||||
'10.0.0.0/8' : `${lan_ip}`
|
||||
}
|
||||
const { networkInterfaces } = require('os');
|
||||
const { Netmask } = require('netmask');
|
||||
|
||||
const resolverFunction = (address) => {
|
||||
for (const network in networks) {
|
||||
try {
|
||||
const mask = new Netmask(network);
|
||||
if (mask.contains(address)) return networks[network];
|
||||
}
|
||||
catch (err) {
|
||||
logger.error('Error checking source network', err);
|
||||
const nets = networkInterfaces();
|
||||
function getNetworks() {
|
||||
let networks = {};
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
networks[net.address + "/24"] = net.address
|
||||
}
|
||||
}
|
||||
}
|
||||
return '127.0.0.1';
|
||||
return networks;
|
||||
}
|
||||
|
||||
const resolverFunction = (address) => {
|
||||
// const networks = {
|
||||
// '$GATEWAY_IP/32': `${public_ip}`,
|
||||
// '10.0.0.0/8' : `${lan_ip}`
|
||||
// }
|
||||
const networks = getNetworks();
|
||||
for (const network in networks) {
|
||||
if (new Netmask(network).contains(address)) {
|
||||
return networks[network];
|
||||
}
|
||||
}
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
new FtpSrv({pasv_url: resolverFunction});
|
||||
@@ -345,8 +365,8 @@ __Used in:__ `RNFR`, `RNTO`
|
||||
Modifies a file or directory's permissions
|
||||
__Used in:__ `SITE CHMOD`
|
||||
|
||||
#### [`getUniqueName()`](src/fs.js#L131)
|
||||
Returns a unique file name to write to
|
||||
#### [`getUniqueName(fileName)`](src/fs.js#L131)
|
||||
Returns a unique file name to write to. Client requested filename available if you want to base your function on it.
|
||||
__Used in:__ `STOU`
|
||||
|
||||
## Contributing
|
||||
|
||||
2
ftp-srv.d.ts
vendored
2
ftp-srv.d.ts
vendored
@@ -38,7 +38,7 @@ export class FileSystem {
|
||||
|
||||
chmod(path: string, mode: string): Promise<any>;
|
||||
|
||||
getUniqueName(): string;
|
||||
getUniqueName(fileName: string): string;
|
||||
}
|
||||
|
||||
export class FtpConnection extends EventEmitter {
|
||||
|
||||
20550
package-lock.json
generated
20550
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -31,8 +31,10 @@
|
||||
},
|
||||
"release": {
|
||||
"verifyConditions": "condition-circle",
|
||||
"branch": "master",
|
||||
"branches": ["master"]
|
||||
"branch": "main",
|
||||
"branches": [
|
||||
"main"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@@ -42,8 +44,7 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"commitlint": {
|
||||
@@ -74,16 +75,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^10.0.0",
|
||||
"@commitlint/config-conventional": "^8.1.0",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@icetee/ftp": "^1.0.2",
|
||||
"chai": "^4.2.0",
|
||||
"condition-circle": "^2.0.2",
|
||||
"eslint": "^5.14.1",
|
||||
"husky": "^1.3.1",
|
||||
"lint-staged": "^8.2.1",
|
||||
"mocha": "^8.1.1",
|
||||
"lint-staged": "^12.3.7",
|
||||
"mocha": "^9.2.2",
|
||||
"rimraf": "^2.6.1",
|
||||
"semantic-release": "^17.2.3",
|
||||
"semantic-release": "^19.0.2",
|
||||
"sinon": "^2.3.5"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.mkdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return Promise.try(() => this.fs.mkdir(command.arg))
|
||||
return Promise.try(() => this.fs.mkdir(command.arg, { recursive: true }))
|
||||
.then((dir) => {
|
||||
const path = dir ? `"${escapePath(dir)}"` : undefined;
|
||||
return this.reply(257, path);
|
||||
|
||||
@@ -9,7 +9,7 @@ module.exports = {
|
||||
|
||||
const fileName = args.command.arg;
|
||||
return Promise.try(() => this.fs.get(fileName))
|
||||
.then(() => Promise.try(() => this.fs.getUniqueName()))
|
||||
.then(() => Promise.try(() => this.fs.getUniqueName(fileName)))
|
||||
.catch(() => fileName)
|
||||
.then((name) => {
|
||||
args.command.arg = name;
|
||||
|
||||
@@ -119,7 +119,7 @@ class FileSystem {
|
||||
|
||||
mkdir(path) {
|
||||
const {fsPath} = this._resolvePath(path);
|
||||
return fsAsync.mkdir(fsPath)
|
||||
return fsAsync.mkdir(fsPath, { recursive: true })
|
||||
.then(() => fsPath);
|
||||
}
|
||||
|
||||
|
||||
23
src/index.js
23
src/index.js
@@ -117,17 +117,22 @@ class FtpServer extends EventEmitter {
|
||||
}
|
||||
|
||||
disconnectClient(id) {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = this.connections[id];
|
||||
if (!client) return resolve();
|
||||
delete this.connections[id];
|
||||
|
||||
setTimeout(() => {
|
||||
reject('Timed out disconnecting client')
|
||||
}, this.options.timeout || 1000)
|
||||
|
||||
try {
|
||||
client.close(0);
|
||||
} catch (err) {
|
||||
this.log.error(err, 'Error closing connection', {id});
|
||||
} finally {
|
||||
resolve('Disconnected');
|
||||
}
|
||||
|
||||
resolve('Disconnected');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,16 +142,22 @@ class FtpServer extends EventEmitter {
|
||||
}
|
||||
|
||||
close() {
|
||||
this.log.info('Server closing...');
|
||||
this.server.maxConnections = 0;
|
||||
return Promise.map(Object.keys(this.connections), (id) => Promise.try(this.disconnectClient.bind(this, id)))
|
||||
this.log.info('Closing connections:', Object.keys(this.connections).length);
|
||||
|
||||
return Promise.all(Object.keys(this.connections).map((id) => this.disconnectClient(id)))
|
||||
.then(() => new Promise((resolve) => {
|
||||
this.server.close((err) => {
|
||||
this.log.info('Server closing...');
|
||||
if (err) this.log.error(err, 'Error closing server');
|
||||
resolve('Closed');
|
||||
});
|
||||
}))
|
||||
.then(() => this.removeAllListeners());
|
||||
.then(() => {
|
||||
this.log.debug('Removing event listeners...')
|
||||
this.removeAllListeners();
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,10 +24,13 @@ describe('Integration', function () {
|
||||
before(() => {
|
||||
return startServer({url: 'ftp://127.0.0.1:8880'});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create().usingPromise(Promise);
|
||||
});
|
||||
|
||||
afterEach(() => sandbox.restore());
|
||||
|
||||
after(() => server.close());
|
||||
|
||||
before(() => {
|
||||
@@ -101,7 +104,8 @@ describe('Integration', function () {
|
||||
if (stat.isDirectory()) directoryPurge(item);
|
||||
else fs.unlinkSync(item);
|
||||
});
|
||||
fs.rmdirSync(dir);
|
||||
|
||||
fs.rmdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function runFileSystemTests(name) {
|
||||
@@ -259,13 +263,16 @@ describe('Integration', function () {
|
||||
client.get('tést.txt', (err, stream) => {
|
||||
expect(err).to.not.exist;
|
||||
let text = '';
|
||||
|
||||
stream.on('data', (data) => {
|
||||
text += data.toString();
|
||||
});
|
||||
stream.on('end', () => {
|
||||
|
||||
stream.on('finish', () => {
|
||||
expect(text).to.equal('test text file, awesome!');
|
||||
done();
|
||||
});
|
||||
|
||||
stream.resume();
|
||||
});
|
||||
});
|
||||
@@ -338,7 +345,25 @@ describe('Integration', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('MKD témp multiple levels deep', (done) => {
|
||||
const path = `${clientDirectory}/${name}/témp`;
|
||||
if (fs.existsSync(path)) {
|
||||
fs.rmdirSync(path, {recursive: true});
|
||||
}
|
||||
|
||||
client.mkdir('témp/first/second', (err) => {
|
||||
expect(err).to.not.exist;
|
||||
expect(fs.existsSync(path)).to.equal(true);
|
||||
|
||||
fs.rmdirSync(path, {recursive: true});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('CWD témp', (done) => {
|
||||
const path = `${clientDirectory}/${name}/témp`;
|
||||
fs.mkdirSync(path)
|
||||
|
||||
client.cwd('témp', (err, data) => {
|
||||
expect(err).to.not.exist;
|
||||
expect(data).to.to.be.a('string');
|
||||
|
||||
Reference in New Issue
Block a user