├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── index.js ├── package.json └── test └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true, 10 | mocha: true 11 | }, 12 | extends: 'standard', 13 | // add your custom rules here 14 | rules: { 15 | // allow debugger during development 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 17 | // do not allow console.logs etc... 18 | 'no-console': 2 19 | }, 20 | globals: {} 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Filesystem 2 | *~ 3 | #*# 4 | .DS_Store 5 | 6 | # dependencies 7 | yarn.lock 8 | node_modules 9 | 10 | # logs 11 | npm-debug.log 12 | 13 | # Examples 14 | examples 15 | 16 | # Coverage support 17 | coverage 18 | *.lcov 19 | .nyc_output 20 | 21 | # IDE default folder 22 | .idea/* 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7.2" 4 | - "6.9" 5 | - "5.12" 6 | - "4.7" 7 | - "0.12" 8 | install: 9 | - sudo apt-get install lftp 10 | - npm install 11 | script: 12 | - npm test 13 | # after_success: 14 | # - npm run coverage 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Sebastien Chopin ([@Atinux](https://github.com/Atinux)) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-ftps 2 | ========= 3 |

4 | Build Status 5 | Version 6 | Downloads 7 | License 8 |

9 | 10 | FTP, FTPS and SFTP client for node.js, mainly a `lftp` wrapper. 11 | 12 | Requirements 13 | ------------ 14 | 15 | You need to have the executable `lftp` installed on your computer. 16 | 17 | [LFTP Homepage](http://lftp.yar.ru/) 18 | 19 | **Windows** ([Chocolatey](https://chocolatey.org/)) 20 | ```cmd 21 | C:\> choco install lftp 22 | ``` 23 | **OSX** ([Homebrew](http://brew.sh/)) 24 | ```bash 25 | sudo brew install lftp 26 | ``` 27 | **Linux** 28 | ```bash 29 | sudo apt-get install lftp 30 | # or 31 | sudo yum install lftp 32 | ``` 33 | 34 | Installation 35 | ----------- 36 | 37 | ``` sh 38 | npm install ftps 39 | ``` 40 | 41 | Usage 42 | ----- 43 | 44 | ``` js 45 | var FTPS = require('ftps'); 46 | var ftps = new FTPS({ 47 | host: 'domain.com', // required 48 | username: 'Test', // Optional. Use empty username for anonymous access. 49 | password: 'Test', // Required if username is not empty, except when requiresPassword: false 50 | protocol: 'sftp', // Optional, values : 'ftp', 'sftp', 'ftps', ... default: 'ftp' 51 | // protocol is added on beginning of host, ex : sftp://domain.com in this case 52 | port: 22, // Optional 53 | // port is added to the end of the host, ex: sftp://domain.com:22 in this case 54 | escape: true, // optional, used for escaping shell characters (space, $, etc.), default: true 55 | retries: 2, // Optional, defaults to 1 (1 = no retries, 0 = unlimited retries) 56 | timeout: 10, // Optional, Time before failing a connection attempt. Defaults to 10 57 | retryInterval: 5, // Optional, Time in seconds between attempts. Defaults to 5 58 | retryMultiplier: 1, // Optional, Multiplier by which retryInterval is multiplied each time new attempt fails. Defaults to 1 59 | requiresPassword: true, // Optional, defaults to true 60 | autoConfirm: true, // Optional, is used to auto confirm ssl questions on sftp or fish protocols, defaults to false 61 | cwd: '', // Optional, defaults to the directory from where the script is executed 62 | additionalLftpCommands: '', // Additional commands to pass to lftp, splitted by ';' 63 | requireSSHKey: true, // Optional, defaults to false, This option for SFTP Protocol with ssh key authentication 64 | sshKeyPath: '/home1/phrasee/id_dsa', // Required if requireSSHKey: true , defaults to empty string, This option for SFTP Protocol with ssh key authentication 65 | sshKeyOptions: '' // ssh key options such as 'StrictHostKeyChecking=no' 66 | }); 67 | // Do some amazing things 68 | ftps.cd('some_directory').addFile(__dirname + '/test.txt').exec(console.log); 69 | ``` 70 | 71 | Documentation 72 | ------------------ 73 | 74 | Here are some of the chainable functions : 75 | 76 | ``` js 77 | ftps.ls() 78 | ftps.pwd() 79 | ftps.cd(directory) 80 | ftps.cat(pathToRemoteFiles) 81 | ftps.put(pathToLocalFile, [pathToRemoteFile]) // alias: addFile 82 | ftps.get(pathToRemoteFile, [pathToLocalFile]) // download remote file and save to local path (if not given, use same name as remote file), alias: getFile 83 | ftps.mv(from, to) // alias move 84 | ftps.rm(file1, file2, ...) // alias remove 85 | ftps.mkdir(pathToNewDir, mode) 86 | ftps.rmdir(directory1, directory2, ...) 87 | ftps.mirror({ 88 | remoteDir: '.', // optional, default: . 89 | localDir: '.', // optional: default: . 90 | filter: /\.pdf$/, // optional, filter the files synchronized 91 | parallel: true / Integer, // optional, default: false 92 | upload: true, // optional, default: false, to upload the files from the locaDir to the remoteDir 93 | }) 94 | ``` 95 | 96 | If you want to escape some arguments because you used "escape: false" in the options: 97 | ```js 98 | ftps.escapeshell('My folder'); 99 | // Return 'My\\ \\$folder' 100 | ``` 101 | 102 | Execute a command on the remote server: 103 | ```js 104 | ftps.raw('ls -l') 105 | ``` 106 | 107 | To see all available commands: [LFTP Commands](http://lftp.yar.ru/lftp-man.html) 108 | 109 | For information, ls, pwd, ... rm are just some alias of raw() method. 110 | 111 | Run the commands 112 | ``` js 113 | ftps.exec(function (err, res) { 114 | // err will be null (to respect async convention) 115 | // res is an hash with { error: stderr || null, data: stdout } 116 | }); 117 | // exec() return the child process of the spawn() method 118 | ``` 119 | 120 | Also, take note that if a command fails it will not stop the next commands from executing, for example: 121 | ``` js 122 | ftps.cd('non-existing-dir/').addFile('./test.txt').exec(console.log); 123 | /* 124 | Will add file on ~/ and give: 125 | { 126 | error: 'cd: non-existing-dir: No such file or directory\n', 127 | data: '' 128 | } 129 | So...be cautious because ./test.txt has been added 130 | */ 131 | 132 | ``` 133 | 134 | For using the stream, use the `execAsStream()` command which returns a stream. 135 | ```js 136 | var ftps = new lftp(config) 137 | var stream = ftps.raw('find').execAsStream() 138 | stream.pipe(process.stdout) 139 | ``` 140 | 141 | 142 | 143 | Note on Using LFTP Commands 144 | ------------------ 145 | 146 | Normally in the lftp cli you would make a file of `set` commands and pass that file name into lftp with the `-c` option. However, ftps will do that for you with the `additionalLftpCommands` option. 147 | 148 | For instance, to connect to a legacy sftp server you can do: 149 | 150 | ```JS 151 | 152 | const ftps = new FTPS({ 153 | // ... 154 | additionalLftpCommands: 'set sftp:connect-program "ssh -a -x -o KexAlgorithms=diffie-hellman-group1-sha1"', 155 | // Additional commands to pass to lftp, splitted by ';' 156 | requireSSHKey: false, 157 | }); 158 | 159 | // this is helpful for people getting the DH GEX group out of range error 160 | 161 | ``` 162 | 163 | This is also instead of making a `~/.lftprc` file. As you can see, you just put anything that would go into the command file into the option separated by a `;`. 164 | 165 | Additional command can be found [here](http://lftp.tech/lftp-man.html) or by running `man lftp`. 166 | 167 | ----- 168 | 169 | 170 | 171 | PS: Any pull requests are welcome :-) 172 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var spawn = require('child_process').spawn 4 | var _ = require('lodash') 5 | var dcp = require('duplex-child-process') 6 | 7 | /* 8 | ** Params : 9 | ** { 10 | ** host: 'domain.com', // Required 11 | ** username: 'Test', // Optional. Use empty username for anonymous access. 12 | ** password: 'Test', // Required if username is not empty, except when requiresPassword: false, 13 | ** bareCredentials: 'Test,Test$T', // Used instead of username/password for special cases where passwords should not be escaped 14 | ** protocol: 'sftp', // Optional, values : 'ftp', 'sftp', 'ftps', ... default: 'ftp' 15 | ** // protocol is added on beginning of host, ex : sftp://domain.com in this case 16 | ** port: '22', // Optional 17 | ** // port is added at the end of the host, ex : sftp://domain.com:28 in this case 18 | ** escape: true, // optional, used for escaping shell characters (space, $, etc.), default: true 19 | ** retries: 2, // Optional, defaults to 1 (1 = no retries, 0 = unlimited retries) 20 | ** timeout: 10, // Optional, Time before failing a connection attempt. Defaults to 10 21 | ** retryInterval: 5, // Optional, Time in seconds between attempts. Defaults to 5 22 | ** retryMultiplier: 1, // Optional, Multiplier by which retryInterval is multiplied each time new attempt fails. Defaults to 1 23 | ** requiresPassword: true, // Optional, defaults to true 24 | ** autoConfirm: true, // Optional, defaults to false 25 | ** cwd: '', // Optional, defaults to the directory from where the script is executed 26 | ** additionalLftpCommands: '', // Additional commands to pass to lftp, splitted by ';' 27 | ** requireSSHKey: false, // This option for SFTP Protocol with ssh key authentication 28 | ** sshKeyPath: '', // ssh key path for, SFTP Protocol with ssh key authentication 29 | ** sshKeyOptions: '' // ssh key options such as 'StrictHostKeyChecking=no' 30 | ** } 31 | ** 32 | ** Usage : 33 | ** ftp.cd('some_directory').rm('./test.txt').exec(console.log) 34 | */ 35 | 36 | var FTP = function (options) { 37 | this.initialize(options) 38 | this.cmds = [] 39 | } 40 | 41 | FTP.prototype.initialize = function (options) { 42 | var defaults = { 43 | host: '', 44 | protocol: 'ftp', 45 | username: '', 46 | password: '', 47 | bareCredentials: '', 48 | escape: true, 49 | retries: 1, // LFTP by default tries an unlimited amount of times so we change that here 50 | timeout: 10, // Time before failing a connection attempt 51 | retryInterval: 5, // Time in seconds between attempts 52 | retryIntervalMultiplier: 1, // Multiplier by which retryInterval is multiplied each time new attempt fails 53 | requiresPassword: true, // Supports Anonymous FTP 54 | autoConfirm: false, // Auto confirm ssl certificate, 55 | cwd: '', // Use a different working directory 56 | additionalLftpCommands: '', // Additional commands to pass to lftp, splitted by ';' 57 | requireSSHKey: false, // This option for SFTP Protocol with ssh key authentication 58 | sshKeyPath: '', // ssh key path for, SFTP Protocol with ssh key authentication\ 59 | sshKeyOptions: '' // ssh key options such as 'StrictHostKeyChecking=no' 60 | } 61 | 62 | // Extend options with defaults 63 | var opts = _.pick(_.extend(defaults, options), 'host', 'username', 'password', 'bareCredentials', 'port', 'escape', 'retries', 'timeout', 'retryInterval', 'retryIntervalMultiplier', 'requiresPassword', 'protocol', 'autoConfirm', 'cwd', 'additionalLftpCommands', 'requireSSHKey', 'sshKeyPath', 'sshKeyOptions') 64 | 65 | // Validation 66 | if (!opts.host) throw new Error('You need to set a host.') 67 | if (opts.username && opts.requiresPassword === true && !opts.password && !opts.bareCredentials) throw new Error('You need to set a password.') 68 | if (opts.protocol && typeof opts.protocol !== 'string') throw new Error('Protocol needs to be of type string.') 69 | if (opts.requireSSHKey === true && !opts.sshKeyPath) throw new Error('You need to set a ssh key path.'); 70 | 71 | // Setting options 72 | if (opts.protocol && opts.host.indexOf(opts.protocol + '://') !== 0) { 73 | opts.host = opts.protocol + '://' + opts.host 74 | } 75 | if (opts.port) { 76 | opts.host = opts.host + ':' + opts.port 77 | } 78 | this.options = opts 79 | } 80 | 81 | FTP.prototype.escapeshell = function (cmd) { 82 | if (typeof cmd !== 'string') { 83 | return '' 84 | } 85 | return cmd.replace(/([&"\s'$`\\;])/g, '\\$1') 86 | } 87 | 88 | FTP.prototype._escapeshell = function (cmd) { 89 | if (this.options.escape) { 90 | return this.escapeshell(cmd) 91 | } 92 | return cmd 93 | } 94 | 95 | FTP.prototype.prepareLFTPOptions = function () { 96 | var opts = [] 97 | 98 | // Only support SFTP or FISH for ssl autoConfirm 99 | if ((this.options.protocol.toLowerCase() === 'sftp' || this.options.protocol.toLowerCase() === 'fish') && this.options.autoConfirm) { 100 | opts.push('set ' + this.options.protocol.toLowerCase() + ':auto-confirm yes') 101 | } 102 | // Only support SFTP for openSSH key authentication 103 | if(this.options.protocol.toLowerCase() === "sftp" && this.options.requireSSHKey){ 104 | var extra = (this.options.sshKeyOptions || '') 105 | .split(' ') 106 | .filter(Boolean) 107 | .map(function (extra) { 108 | return ' -o ' + extra 109 | }) 110 | .join('') 111 | 112 | opts.push('set sftp:connect-program "ssh' + extra + ' -a -x -i '+this.options.sshKeyPath+'"') 113 | } 114 | opts.push('set net:max-retries ' + this.options.retries) 115 | opts.push('set net:timeout ' + this.options.timeout) 116 | opts.push('set net:reconnect-interval-base ' + this.options.retryInterval) 117 | opts.push('set net:reconnect-interval-multiplier ' + this.options.retryIntervalMultiplier) 118 | opts.push(this.options.additionalLftpCommands) 119 | 120 | var open = 'open' 121 | if (this.options.bareCredentials) { 122 | open += ' -u \'' + this.options.bareCredentials + '\'' 123 | } 124 | else if (this.options.username) { 125 | open += ' -u "' + this._escapeshell(this.options.username) + '","' + this._escapeshell(this.options.password) + '"' 126 | } 127 | open += ' "' + this.options.host + '"' 128 | opts.push(open) 129 | return opts 130 | } 131 | 132 | FTP.prototype.exec = function (cmds, callback) { 133 | if (typeof cmds === 'string') { 134 | cmds = cmds.split(';') 135 | } 136 | if (Array.isArray(cmds)) { 137 | this.cmds = this.cmds.concat(cmds) 138 | } 139 | if (typeof cmds === 'function' && !callback) { 140 | callback = cmds 141 | } 142 | if (!callback) { 143 | throw new Error('Callback is missing to exec() function.') 144 | } 145 | var cmd = this.prepareLFTPOptions().concat(this.cmds).join(';') 146 | var spawnoptions 147 | 148 | this.cmds = [] 149 | if (this.options.cwd) { 150 | spawnoptions = { 151 | cwd: this.options.cwd 152 | } 153 | } 154 | 155 | var lftp = spawn('lftp', ['-c', cmd], spawnoptions) 156 | var data = '' 157 | var error = '' 158 | 159 | lftp.stdout.on('data', function (res) { 160 | data += res 161 | }) 162 | lftp.stderr.on('data', function (res) { 163 | error += res 164 | }) 165 | lftp.on('error', function (err) { 166 | if (callback) { 167 | callback(err, { 168 | error: error || null, 169 | data: data 170 | }) 171 | } 172 | callback = null // Make sure callback is only called once, whether 'exit' event is triggered or not. 173 | }) 174 | lftp.on('close', function (code) { 175 | if (callback) { 176 | callback(null, { 177 | error: error || null, 178 | data: data 179 | }) 180 | } 181 | }) 182 | return lftp 183 | } 184 | 185 | FTP.prototype.execAsStream = function (cmds) { 186 | if (typeof cmds === 'string') { 187 | cmds = cmds.split(';') 188 | } 189 | if (Array.isArray(cmds)) { 190 | this.cmds = this.cmds.concat(cmds) 191 | } 192 | 193 | var cmd = this.prepareLFTPOptions().concat(this.cmds).join(';') 194 | var spawnoptions 195 | 196 | this.cmds = [] 197 | if (this.options.cwd) { 198 | spawnoptions = { 199 | cwd: this.options.cwd 200 | } 201 | } 202 | 203 | return dcp.spawn('lftp', ['-c', cmd], spawnoptions) 204 | } 205 | 206 | FTP.prototype.raw = function (cmd) { 207 | if (cmd && typeof cmd === 'string') { 208 | this.cmds.push(cmd) 209 | } 210 | return this 211 | } 212 | FTP.prototype.ls = function () { 213 | return this.raw('ls') 214 | } 215 | FTP.prototype.pwd = function () { 216 | return this.raw('pwd') 217 | } 218 | FTP.prototype.cd = function (directory) { 219 | return this.raw('cd ' + this._escapeshell(directory)) 220 | } 221 | FTP.prototype.cat = function (path) { 222 | return this.raw('cat ' + this._escapeshell(path)) 223 | } 224 | FTP.prototype.put = function (localPath, remotePath) { 225 | if (!localPath) { 226 | return this 227 | } 228 | if (!remotePath) { 229 | return this.raw('put ' + this._escapeshell(localPath)) 230 | } 231 | return this.raw('put ' + this._escapeshell(localPath) + ' -o ' + this._escapeshell(remotePath)) 232 | } 233 | FTP.prototype.addFile = FTP.prototype.put 234 | FTP.prototype.get = function (remotePath, localPath) { 235 | if (!remotePath) { 236 | return this 237 | } 238 | if (!localPath) { 239 | return this.raw('get ' + this._escapeshell(remotePath)) 240 | } 241 | return this.raw('get ' + this._escapeshell(remotePath) + ' -o ' + this._escapeshell(localPath)) 242 | } 243 | FTP.prototype.getFile = FTP.prototype.get 244 | FTP.prototype.mv = function (from, to) { 245 | if (!from || !to) { 246 | return this 247 | } 248 | return this.raw('mv ' + this._escapeshell(from) + ' ' + this._escapeshell(to)) 249 | } 250 | FTP.prototype.move = FTP.prototype.mv 251 | FTP.prototype.rm = function () { 252 | return this.raw('rm ' + Array.prototype.slice.call(arguments).map(this._escapeshell.bind(this)).join(' ')) 253 | } 254 | FTP.prototype.remove = FTP.prototype.rm 255 | FTP.prototype.rmdir = function () { 256 | return this.raw('rmdir ' + Array.prototype.slice.call(arguments).map(this._escapeshell.bind(this)).join(' ')) 257 | } 258 | FTP.prototype.mirror = function (opts) { 259 | opts = opts || {} 260 | opts.remoteDir = opts.remoteDir || '.' 261 | opts.localDir = opts.localDir || '.' 262 | opts.options = opts.options || '' 263 | var raw = 'mirror' 264 | if (opts.upload === true) { 265 | raw += ' -R' 266 | } 267 | if (opts.parallel === true) { 268 | raw += ' --parallel' 269 | } 270 | if (typeof opts.parallel === 'number' && opts.parallel >= 1) { 271 | raw += ' --parallel=' + parseInt(opts.parallel, 10) 272 | } 273 | if (opts.options) { 274 | raw += ' ' + opts.options 275 | } 276 | if (opts.filter) { 277 | raw += ' -i "' + String(opts.filter).slice(1, -1) + '"' 278 | } 279 | if (opts.upload === true) { 280 | raw += ' ' + this._escapeshell(opts.localDir) + ' ' + this._escapeshell(opts.remoteDir) 281 | } else { 282 | raw += ' ' + this._escapeshell(opts.remoteDir) + ' ' + this._escapeshell(opts.localDir) 283 | } 284 | return this.raw(raw) 285 | } 286 | 287 | FTP.prototype.mkdir = function (directory, mode) { 288 | return this.raw('mkdir ' + mode + ' ' + directory) 289 | }; 290 | 291 | module.exports = FTP 292 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ftps", 3 | "version": "1.2.0", 4 | "scripts": { 5 | "lint": "eslint index.js test/", 6 | "test": "mocha", 7 | "precommit": "npm run lint" 8 | }, 9 | "main": "./index.js", 10 | "author": { 11 | "name": "Sébastien Chopin", 12 | "email": "atinux@gmail.com", 13 | "url": "https://twitter.com/atinux" 14 | }, 15 | "contributors": [ 16 | { 17 | "name": "Steven Kissack", 18 | "url": "https://github.com/stevokk" 19 | }, 20 | { 21 | "name": "Tyler Finethy", 22 | "url": "https://github.com/tylfin" 23 | }, 24 | { 25 | "name": "@jeromew", 26 | "url": "https://github.com/jeromew" 27 | } 28 | ], 29 | "maintainers": [ 30 | "atinux" 31 | ], 32 | "description": "FTP, FTPS and SFTP client for node.js, mainly a lftp wrapper.", 33 | "keywords": [ 34 | "ftp", 35 | "ftps", 36 | "sftp", 37 | "node-ftp", 38 | "node-sftp", 39 | "node-ftps", 40 | "lftp" 41 | ], 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/Atinux/node-ftps.git" 45 | }, 46 | "engines": { 47 | "node": ">=0.4.0" 48 | }, 49 | "dependencies": { 50 | "duplex-child-process": "0.0.5", 51 | "lodash": "^4.4.0" 52 | }, 53 | "devDependencies": { 54 | "babel-eslint": "^7.1.1", 55 | "eslint": "^3.12.2", 56 | "eslint-config-standard": "^6.2.1", 57 | "eslint-plugin-html": "^1.7.0", 58 | "eslint-plugin-promise": "^3.4.0", 59 | "eslint-plugin-standard": "^2.0.1", 60 | "mocha": "^3.2.0", 61 | "q": "^1.4.1" 62 | }, 63 | "license": "MIT" 64 | } 65 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | var assert = require('assert') 3 | var Q = require('q') 4 | var _ = require('lodash') 5 | // FTPS package from index.js 6 | var FTPS = require('../') 7 | 8 | describe('FTPS', function () { 9 | describe('#initialize()', function () { 10 | it('should properly set default options', function () { 11 | // fake options to avoid throwing an error 12 | var fakeOptions = { 13 | host: 'foobar.com', 14 | username: 'foo', 15 | requiresPassword: false 16 | } 17 | // expectedDefaults as configured in initialize 18 | var expectedDefaults = { 19 | host: 'ftp://' + fakeOptions.host, 20 | protocol: 'ftp', 21 | username: fakeOptions.username, 22 | password: '', 23 | escape: true, 24 | retries: 1, 25 | timeout: 10, 26 | retryInterval: 5, 27 | retryIntervalMultiplier: 1, 28 | requiresPassword: fakeOptions.requiresPassword, 29 | autoConfirm: false, 30 | cwd: '', 31 | additionalLftpCommands: '', 32 | requireSSHKey: false, 33 | sshKeyPath: '' 34 | } 35 | // initialize and grab the options from FTPS 36 | var ftpsDefaultOptions = new FTPS(fakeOptions).options 37 | // assert that the defaultOptions are as expected 38 | assert.equal(true, _.isEqual(ftpsDefaultOptions, expectedDefaults)) 39 | }) 40 | it('should throw an error when host is not set', function () { 41 | try { 42 | var ftpsWithoutHost = new FTPS() 43 | } catch (ex) { 44 | assert.equal('Error: You need to set a host.', ex) 45 | } 46 | assert.equal(ftpsWithoutHost, undefined) 47 | }) 48 | it('should not throw an error when username is not set', function () { 49 | var fakeOptionsNoUsername = { 50 | host: 'foobar' 51 | } 52 | var ftpsWithoutUsername = new FTPS(fakeOptionsNoUsername) 53 | assert.equal(ftpsWithoutUsername instanceof FTPS, true) 54 | }) 55 | it('should not pass user,pass to lftp when username is empty', function () { 56 | var fakeOptionsNoUsername = { 57 | host: 'foobar' 58 | } 59 | var ftpsWithoutUsername = new FTPS(fakeOptionsNoUsername) 60 | var cmd = ftpsWithoutUsername.prepareLFTPOptions() 61 | assert.equal(cmd[cmd.length - 1], 'open "ftp://foobar"') 62 | }) 63 | it('should pass user,pass to lftp when username is not empty', function () { 64 | var fakeOptionsNoUsername = { 65 | host: 'foobar', 66 | username: 'user', 67 | password: 'pass' 68 | } 69 | var ftpsWithoutUsername = new FTPS(fakeOptionsNoUsername) 70 | var cmd = ftpsWithoutUsername.prepareLFTPOptions() 71 | assert.equal(cmd[cmd.length - 1], 'open -u "user","pass" "ftp://foobar"') 72 | }) 73 | it('should throw an error when password is not set & requiresPassword is true', function () { 74 | var fakeOptionsWithoutPassword = { 75 | host: 'foobar', 76 | username: 'foo' 77 | } 78 | try { 79 | var ftpsWithoutPassword = new FTPS(fakeOptionsWithoutPassword) 80 | } catch (ex) { 81 | assert.equal('Error: You need to set a password.', ex) 82 | } 83 | assert.equal(ftpsWithoutPassword, undefined) 84 | }) 85 | it('should not throw error when password is not set & requiresPassword is false', function () { 86 | var fakeOptionsRequiresPasswordFalse = { 87 | host: 'foobar', 88 | username: 'foo', 89 | requiresPassword: false 90 | } 91 | var ftpsRequiresPasswordFalse = new FTPS(fakeOptionsRequiresPasswordFalse) 92 | assert.equal(ftpsRequiresPasswordFalse instanceof FTPS, true) 93 | }) 94 | it('should properly set the protocol', function () { 95 | var setProtocol = 'SFTP' 96 | var fakeOptionsWithProtocol = { 97 | host: 'foobar', 98 | username: 'foo', 99 | requiresPassword: false, 100 | protocol: setProtocol 101 | } 102 | var ftpsWithProtocolOptions = new FTPS(fakeOptionsWithProtocol).options 103 | assert.equal(setProtocol, ftpsWithProtocolOptions.protocol) 104 | }) 105 | it('should raise error when protocol not set to string', function () { 106 | var setProtocol = 111 107 | var fakeOptionsWithProtocol = { 108 | host: 'foobar', 109 | username: 'foo', 110 | requiresPassword: false, 111 | protocol: setProtocol 112 | } 113 | try { 114 | new FTPS(fakeOptionsWithProtocol).options 115 | } catch (ex) { 116 | assert.equal('Error: Protocol needs to be of type string.', ex) 117 | } 118 | }) 119 | }) 120 | describe('#rm()', function () { 121 | beforeEach(function () { 122 | var fakeOptions = { 123 | host: 'foobar.com', 124 | username: 'foo', 125 | requiresPassword: false 126 | } 127 | this.ftps = new FTPS(fakeOptions) 128 | }) 129 | it('should not have TypeError: Cannot read property \'escape\' of undefined', function () { 130 | this.ftps.rm('foobar.txt') 131 | }) 132 | }) 133 | describe('#rmdir()', function () { 134 | beforeEach(function () { 135 | var fakeOptions = { 136 | host: 'foobar.com', 137 | username: 'foo', 138 | requiresPassword: false 139 | } 140 | this.ftps = new FTPS(fakeOptions) 141 | }) 142 | it('should not have TypeError: Cannot read property \'escape\' of undefined', function () { 143 | this.ftps.rmdir('foo') 144 | }) 145 | }) 146 | describe('#exec()', function () { 147 | beforeEach(function () { 148 | // initialize multiple ftps connections for testing purposes 149 | var fakeOptions = { 150 | host: 'foo', 151 | username: 'fake', 152 | password: 'fake', 153 | port: '11111', 154 | protocol: 'sftp' 155 | } 156 | // arbitrarily picked 4, the on 'exit' bug only occurs when one LFTP 157 | // exit causes other clients to prematurely terminate 158 | this.ftps = new FTPS(fakeOptions) 159 | this.ftps2 = new FTPS(fakeOptions) 160 | this.ftps3 = new FTPS(fakeOptions) 161 | this.ftps4 = new FTPS(fakeOptions) 162 | }) 163 | it('should send error for each misconfigured ftps clients', function (done) { 164 | // run ninvoke to async run all ftps connections and perform a ls that 165 | // is expected to fail 166 | var promises = [] 167 | promises.push(Q.ninvoke(this.ftps.ls(), 'exec')) 168 | promises.push(Q.ninvoke(this.ftps2.ls(), 'exec')) 169 | promises.push(Q.ninvoke(this.ftps3.ls(), 'exec')) 170 | promises.push(Q.ninvoke(this.ftps4.ls(), 'exec')) 171 | Q.all(promises).then(function (res) { 172 | _.forEach(res, function (value) { 173 | // assert that each one has a non-null error 174 | assert.notEqual(value.error, null) 175 | }) 176 | done() 177 | }) 178 | .fail(function (err) { 179 | // propagate error to mocha on failure 180 | done(err) 181 | }) 182 | }) 183 | }) 184 | }) 185 | --------------------------------------------------------------------------------