ssh2-sftp-client 6.0.1 → 7.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,12 @@
1
1
  - [Overview](#sec-1)
2
2
  - [Installation](#sec-2)
3
3
  - [Basic Usage](#sec-3)
4
- - [Version 6.x Changes](#sec-4)
5
- - [Version 6.0.1](#sec-4-1)
6
- - [Version 6.0.0 Changes](#sec-4-2)
4
+ - [Version 7.x Changes](#sec-4)
7
5
  - [Documentation](#sec-5)
8
6
  - [Specifying Paths](#sec-5-1)
9
7
  - [Methods](#sec-5-2)
10
8
  - [new SftpClient(name) ===> SFTP client object](#sec-5-2-1)
11
- - [connect(config) ===> SFTPstream](#sec-5-2-2)
9
+ - [connect(config) ===> SFTP object](#sec-5-2-2)
12
10
  - [list(path, pattern) ==> Array[object]](#sec-5-2-3)
13
11
  - [exists(path) ==> boolean](#sec-5-2-4)
14
12
  - [stat(path) ==> object](#sec-5-2-5)
@@ -31,7 +29,8 @@
31
29
  - [Add and Remove Listeners](#sec-5-2-22)
32
30
  - [Platform Quirks & Warnings](#sec-6)
33
31
  - [Server Capabilities](#sec-6-1)
34
- - [Promises & Events](#sec-6-2)
32
+ - [Promises, Events & Managing Exceptions](#sec-6-2)
33
+ - [Adding Custom Handlers](#sec-6-2-1)
35
34
  - [Windows Based Servers](#sec-6-3)
36
35
  - [Don't Re-use SftpClient Objects](#sec-6-4)
37
36
  - [FAQ](#sec-7)
@@ -41,6 +40,7 @@
41
40
  - [How can I connect through a Socks Proxy](#sec-7-4)
42
41
  - [Timeout while waiting for handshake or handshake errors](#sec-7-5)
43
42
  - [How can I limit upload/download speed](#sec-7-6)
43
+ - [Connection hangs or fails for larger files](#sec-7-7)
44
44
  - [Examples](#sec-8)
45
45
  - [Troubleshooting](#sec-9)
46
46
  - [Common Errors](#sec-9-1)
@@ -59,24 +59,14 @@
59
59
 
60
60
  an SFTP client for node.js, a wrapper around [SSH2](https://github.com/mscdex/ssh2) which provides a high level convenience abstraction as well as a Promise based API.
61
61
 
62
- Documentation on the methods and available options in the underlying modules can be found on the [SSH2](https://github.com/mscdex/ssh2) and [SSH2-STREAMS](https://github.com/mscdex/ssh2-streams/blob/master/SFTPStream.md) project pages.
62
+ Documentation on the methods and available options in the underlying modules can be found on the [SSH2](https://github.com/mscdex/ssh2) project pages.
63
63
 
64
- Current stable release is **v5.3.2**.
64
+ Current stable release is **v7.0.3**.
65
65
 
66
- Code has been tested against Node versions 12.18.2 and 13.14.0
66
+ Code has been tested against Node versions 12.22.1, 14.17.0 and 16.2.0
67
67
 
68
68
  Node versions < 10.x are not supported.
69
69
 
70
- <span class="underline">WARNING</span> There is currently a regression error with versions of node later than version 14.0. It appears that when using streams with chunk sizes which exceed the high water mark for the stream, a drain event is no longer emitted. As a result, streams with sufficient data will hang indefinitely. This appears to affect fastput, fastget, put and possibly get operations. Until this issue is resolved and a new version of ssh2/ssh2-streams is released, using node v14 is not recommended.
71
-
72
- A bug report hass been logged against the ssh2-streams library as [issue 156](https://github.com/mscdex/ssh2-streams/issues/156).
73
-
74
- <span class="underline">UPDATE</span>: The author of the upstream ssh2 and ssh2-streams module has decided on a re-write of the ssh2 module to address the above issues as well as some other design limitations and to allow the module to better fit in with newer versions of node. As part of that process, the functionality of ssh2-streams is being incorporated into the main ssh2 module and the ssh2-streams module is being deprecated. This will require a significant update to this module and may result in some API changes, depending on what changes in the re-write of ssh2.
75
-
76
- To support these changes, a new branch called *version-6* has been created. This branch will use the newest version of ssh2 and for now is very much experimental and subject to change.
77
-
78
- <span class="underline">UPDATE</span>: Apparently the change in core node which cause the issue with ssh2 has been rolled back in node version 15.3.0. Testing seems to indicate the above issue does not exist in that version of node.
79
-
80
70
  # Installation<a id="sec-2"></a>
81
71
 
82
72
  ```shell
@@ -103,23 +93,13 @@ sftp.connect({
103
93
  });
104
94
  ```
105
95
 
106
- # Version 6.x Changes<a id="sec-4"></a>
107
-
108
- ## Version 6.0.1<a id="sec-4-1"></a>
96
+ # Version 7.x Changes<a id="sec-4"></a>
109
97
 
110
- - Fix issue with connect retry not releasing 'ready' listener.
111
- - Add finally clauses to all promises to ensure release of temporary listeners.
112
- - Add nyc module to improve test coverage
113
- - Added additional utils tests to improve test coverage
114
- - Removed some unnecessary util functions to reduce code size
98
+ - This version is based on version 1.x.x of `ssh2`. This version of `ssh2` is a complete re-write of the `ssh2` library. This re-write addresses issues encountered when using node v14 as well as some design weaknesses in the previous 0.8.x version.
115
99
 
116
- ## Version 6.0.0 Changes<a id="sec-4-2"></a>
100
+ - **Breaking Change** Expanded option handling for `get()` and `put()` methods. A number of use cases were identified where setting specific options on the read and write streams and the pipe operation are necessary. For example, disabling `autoClose` on streams or the `end` event in pipes. The `options` argument for `get()` and `put()` calls now supports properties for `readStreamOptions`, `writeStreamOptions` and `pipeOptions`. Note that options are only applied to streams created by the `get()` and `put()` methods. Streams passed into these methods are under the control of the client code and therefore cannot have options supplied in arguments to those streams (you would apply such options when you create the streams). Options are typically only necessary in special use cases. Most of the time, no options are required. However, if you are currently using options to either `put()` or `get()`, you will need to update your code to map these options to the new structure.
117
101
 
118
- - Added new optional argument *notFoundOK* to `delete()` method. If true, no error is thrown when trying to delete a file which does not exist. Default is false.
119
- - Added new filter argument to `uploadDir()` and `downloadDir()` methods. The filter argument is a regular expression used to match the files and directories to be included in the upload or download. Defaults to match all files and directories.
120
- - New event handling approach. Have standardised the adding and removing of event handlers as well as added event handlers for *close* and *end* events. Some sftp servers appear to abruptly terminate connections without issuing an *error* event. This could result in failing to resolve the promise associated with the action being performed. Adding *close* and *end* listeners to reject a promise should prevent the scripts from hanging when the remote server does not return either a status or an error.
121
- - Replace the `retry` library module with the `promise-retry` library module. This module also uses the `retry` library, but at a higher level abstracted for use with Promises. As a result, the number of failed retries is no longer returned as part of the error message when all attempted connection retries fail.
122
- - Reduced argument verification code. Version 5.x added a lot of additional argument verification code in an attempt to provide more meaningful error messages. While this goal was achieved, it did have a significant performance impact, especially with respect to small file transfers. This additional argument verification has now been removed in favour of faster code. The downside is that some error messages have changed and in some cases, will not be as meaningful or will be more generic in nature. This seems as a reasonable compromise. It may result in increased difficulty tracking down why a script is failing, but once a script is working, it should be more efficient. If your code relies on the text in error messages, you will need to verify whether the text is still the same in any testing and adjust where necessary.
102
+ - Improved event handling. A listener for a global error event is now defined to catch errors which occur in-between method calls i.e. connection lost in-between calls to the library methods. A new mechanism has also been added for removal of listeners when no longer required.
123
103
 
124
104
  # Documentation<a id="sec-5"></a>
125
105
 
@@ -129,7 +109,7 @@ All the methods will return a Promise, except for `on()` and `removeListener()`,
129
109
 
130
110
  ## Specifying Paths<a id="sec-5-1"></a>
131
111
 
132
- The convention with both FTP and SFTP is that paths are specified using a 'nix' style i.e. use '*' as the path separator. This means that even if your SFTP server is running on a win32 platform, you should use '*' instead of '\\' as the path separator. For example, for a win32 path of 'C:\Users\fred' you would actually use '/C:/Users/fred'. If your win32 server does not support the 'nix' path convention, you can try setting the `remotePathSep` property of the `SftpClient` object to the path separator of your remote server. This **might** work, but has not been tested. Please let me know if you need to do this and provide details of the SFTP server so that I can try to create an appropriate environment and adjust things as necessary. At this point, I'm not aware of any win32 based SFTP servers which do not support the 'nix' path convention.
112
+ The convention with both FTP and SFTP is that paths are specified using a 'nix' style i.e. use `/` as the path separator. This means that even if your SFTP server is running on a win32 platform, you should use `/` instead of `\` as the path separator. For example, for a win32 path of `C:\Users\fred` you would actually use `/C:/Users/fred`. If your win32 server does not support the 'nix' path convention, you can try setting the `remotePathSep` property of the `SftpClient` object to the path separator of your remote server. This **might** work, but has not been tested. Please let me know if you need to do this and provide details of the SFTP server so that I can try to create an appropriate environment and adjust things as necessary. At this point, I'm not aware of any win32 based SFTP servers which do not support the 'nix' path convention.
133
113
 
134
114
  All remote paths must either be absolute e.g. `/absolute/path/to/file` or they can be relative with a prefix of either `./` (relative to current remote directory) or `../` (relative to parent of current remote directory) e.g. `./relative/path/to/file` or `../relative/to/parent/file`. It is also possible to do things like `../../../file` to specify the parent of the parent of the parent of the current remote directory. The shell tilde (`~`) and common environment variables like `$HOME` are NOT supported.
135
115
 
@@ -171,17 +151,17 @@ Constructor to create a new `ssh2-sftp-client` object. An optional `name` string
171
151
 
172
152
  ```javascript
173
153
  'use strict';
174
-
154
+
175
155
  const Client = require('ssh2-sftp-client');
176
-
156
+
177
157
  const config = {
178
158
  host: 'example.com',
179
159
  username: 'donald',
180
160
  password: 'my-secret'
181
161
  };
182
-
162
+
183
163
  const sftp = new Client('example-client');
184
-
164
+
185
165
  sftp.connect(config)
186
166
  .then(() => {
187
167
  return sftp.cwd();
@@ -195,19 +175,19 @@ Constructor to create a new `ssh2-sftp-client` object. An optional `name` string
195
175
  });
196
176
  ```
197
177
 
198
- ### connect(config) ===> SFTPstream<a id="sec-5-2-2"></a>
178
+ ### connect(config) ===> SFTP object<a id="sec-5-2-2"></a>
199
179
 
200
180
  Connect to an sftp server. Full documentation for connection options is available [here](https://github.com/mscdex/ssh2#user-content-client-methods)
201
181
 
202
182
  1. Connection Options
203
183
 
204
184
  This module is based on the excellent [SSH2](https://github.com/mscdex/ssh2#client) module. That module is a general SSH2 client and server library and provides much more functionality than just SFTP connectivity. Many of the connect options provided by that module are less relevant for SFTP connections. It is recommended you keep the config options to the minimum needed and stick to the options listed in the `commonOpts` below.
205
-
185
+
206
186
  The `retries`, `retry_factor` and `retry_minTimeout` options are not part of the SSH2 module. These are part of the configuration for the [retry](https://www.npmjs.com/package/retry) package and what is used to enable retrying of sftp connection attempts. See the documentation for that package for an explanation of these values.
207
-
187
+
208
188
  ```javascript
209
189
  // common options
210
-
190
+
211
191
  let commonOpts {
212
192
  host: 'localhost', // string Hostname or IP of server.
213
193
  port: 22, // Port number of the server.
@@ -226,9 +206,9 @@ Connect to an sftp server. Full documentation for connection options is availabl
226
206
  retry_factor: 2 // integer. Time factor used to calculate time between retries
227
207
  retry_minTimeout: 2000 // integer. Minimum timeout between attempts
228
208
  };
229
-
209
+
230
210
  // rarely used options
231
-
211
+
232
212
  let advancedOpts {
233
213
  localAddress,
234
214
  localPort,
@@ -269,16 +249,16 @@ Retrieves a directory listing. This method returns a Promise, which once realise
269
249
 
270
250
  ```javascript
271
251
  const Client = require('ssh2-sftp-client');
272
-
252
+
273
253
  const config = {
274
254
  host: 'example.com',
275
255
  port: 22,
276
256
  username: 'red-don',
277
257
  password: 'my-secret'
278
258
  };
279
-
259
+
280
260
  let sftp = new Client;
281
-
261
+
282
262
  sftp.connect(config)
283
263
  .then(() => {
284
264
  return sftp.list('/path/to/remote/dir');
@@ -297,7 +277,7 @@ Retrieves a directory listing. This method returns a Promise, which once realise
297
277
  2. Return Objects
298
278
 
299
279
  The objects in the array returned by `list()` have the following properties;
300
-
280
+
301
281
  ```javascript
302
282
  {
303
283
  type: // file type(-, d, l)
@@ -318,11 +298,11 @@ Retrieves a directory listing. This method returns a Promise, which once realise
318
298
  3. Pattern Filter
319
299
 
320
300
  The filter options can be a regular expression (most powerful option) or a simple *glob*-like string where \* will match any number of characters, e.g.
321
-
301
+
322
302
  foo* => foo, foobar, foobaz
323
303
  *bar => bar, foobar, tabbar
324
304
  *oo* => foo, foobar, look, book
325
-
305
+
326
306
  The *glob*-style matching is very simple. In most cases, you are best off using a real regular expression which will allow you to do more powerful matching and anchor matches to the beginning/end of the string etc.
327
307
 
328
308
  ### exists(path) ==> boolean<a id="sec-5-2-4"></a>
@@ -333,16 +313,16 @@ Tests to see if remote file or directory exists. Returns type of remote object i
333
313
 
334
314
  ```javascript
335
315
  const Client = require('ssh2-sftp-client');
336
-
316
+
337
317
  const config = {
338
318
  host: 'example.com',
339
319
  port: 22,
340
320
  username: 'red-don',
341
321
  password: 'my-secret'
342
322
  };
343
-
323
+
344
324
  let sftp = new Client;
345
-
325
+
346
326
  sftp.connect(config)
347
327
  .then(() => {
348
328
  return sftp.exists('/path/to/remote/dir');
@@ -367,7 +347,7 @@ Returns the attributes associated with the object pointed to by `path`.
367
347
  1. Attributes
368
348
 
369
349
  The `stat()` method returns an object with the following properties;
370
-
350
+
371
351
  ```javascript
372
352
  let stats = {
373
353
  mode: 33279, // integer representing type and permissions
@@ -390,7 +370,7 @@ Returns the attributes associated with the object pointed to by `path`.
390
370
 
391
371
  ```javascript
392
372
  let client = new Client();
393
-
373
+
394
374
  client.connect(config)
395
375
  .then(() => {
396
376
  return client.stat('/path/to/remote/file');
@@ -418,28 +398,33 @@ In general, if your going to pass in a string as the destination, you are better
418
398
 
419
399
  1. Options
420
400
 
421
- The options object can be used to pass options to the underlying readStream used to read the data from the remote server.
422
-
401
+ The `options` argument can be used to pass options to the underlying streams and pipe call used by this method. The argument is an object with three possible properties, `readStreamOptions`, `writeStreamOptions` and `pipeOptions`. The values for each of these properties should be an object containing the required options. For example, possible read stream and pipe options could be defined as
402
+
423
403
  ```javascript
424
- {
425
- flags: 'r',
426
- encoding: null,
427
- handle: null,
428
- mode: 0o666,
429
- autoClose: true
430
- }
404
+ let options = {
405
+ readStreamOptions: {
406
+ flags: 'r',
407
+ encoding: null,
408
+ handle: null,
409
+ mode: 0o666,
410
+ autoClose: true
411
+ },
412
+ pipeOptions: {
413
+ end: false
414
+ }};
415
+
431
416
  ```
432
-
417
+
433
418
  Most of the time, you won't want to use any options. Sometimes, it may be useful to set the encoding. For example, to 'utf-8'. However, it is important not to do this for binary files to avoid data corruption.
434
419
 
435
420
  2. Example Use
436
421
 
437
422
  ```javascript
438
423
  let client = new Client();
439
-
424
+
440
425
  let remotePath = '/remote/server/path/file.txt';
441
426
  let dst = fs.createWriteStream('/local/file/path/copy.txt');
442
-
427
+
443
428
  client.connect(config)
444
429
  .then(() => {
445
430
  return client.get(remotePath, dst);
@@ -451,7 +436,7 @@ In general, if your going to pass in a string as the destination, you are better
451
436
  console.error(err.message);
452
437
  });
453
438
  ```
454
-
439
+
455
440
  - **Tip:** See examples file in the Git repository for more examples. You can pass any writeable stream in as the destination. For example, if you pass in `zlib.createGunzip()` writeable stream, you can both download and decompress a gzip file 'on the fly'.
456
441
 
457
442
  ### fastGet(remotePath, localPath, options) ===> string<a id="sec-5-2-7"></a>
@@ -472,7 +457,7 @@ Downloads a file at remotePath to localPath using parallel reads for faster thro
472
457
  // chunk is transferred
473
458
  }
474
459
  ```
475
-
460
+
476
461
  - **Warning:** Some servers do not respond correctly to requests to alter chunk size. This can result in lost or corrupted data.
477
462
 
478
463
  2. Sample Use
@@ -481,7 +466,7 @@ Downloads a file at remotePath to localPath using parallel reads for faster thro
481
466
  let client = new Client();
482
467
  let remotePath = '/server/path/file.txt';
483
468
  let localPath = '/local/path/file.txt';
484
-
469
+
485
470
  client.connect(config)
486
471
  .then(() => {
487
472
  client.fastGet(remotePath, localPath);
@@ -500,31 +485,32 @@ Upload data from local system to remote server. If the `src` argument is a strin
500
485
 
501
486
  - **src:** string | buffer | readable stream. Data source for data to copy to the remote server.
502
487
  - **remotePath:** string. Path to the remote file to be created on the server.
503
- - **options:** object. Options which can be passed to adjust the write stream used in sending the data to the remote server (see below).
488
+ - **options:** object. Options which can be passed to adjust the read and write stream used in sending the data to the remote server or the pipe call used to make the data transfer (see below).
504
489
 
505
490
  1. Options
506
491
 
507
- The following options are supported;
508
-
492
+ The options object supports three properties, `readStreamOptions`, `writeStreamOptions` and `pipeOptions`. The value for each property should be an object with options as properties and their associated values representing the option value. For example, you might use the following to set `writeStream` options.
493
+
509
494
  ```javascript
510
495
  {
511
- flags: 'w', // w - write and a - append
512
- encoding: null, // use null for binary files
513
- mode: 0o666, // mode to use for created file (rwx)
514
- autoClose: true // automatically close the write stream when finished
515
- }
496
+ writeStreamOptions: {
497
+ flags: 'w', // w - write and a - append
498
+ encoding: null, // use null for binary files
499
+ mode: 0o666, // mode to use for created file (rwx)
500
+ autoClose: true // automatically close the write stream when finished
501
+ }}
516
502
  ```
517
-
503
+
518
504
  The most common options to use are mode and encoding. The values shown above are the defaults. You do not have to set encoding to utf-8 for text files, null is fine for all file types. However, using utf-8 encoding for binary files will often result in data corruption.
519
505
 
520
506
  2. Example Use
521
507
 
522
508
  ```javascript
523
509
  let client = new Client();
524
-
510
+
525
511
  let data = fs.createReadStream('/path/to/local/file.txt');
526
512
  let remote = '/path/to/remote/file.txt';
527
-
513
+
528
514
  client.connect(config)
529
515
  .then(() => {
530
516
  return client.put(data, remote);
@@ -536,7 +522,7 @@ Upload data from local system to remote server. If the `src` argument is a strin
536
522
  console.error(err.message);
537
523
  });
538
524
  ```
539
-
525
+
540
526
  - **Tip:** If the src argument is a path string, consider just using `fastPut()`.
541
527
 
542
528
  ### fastPut(localPath, remotePath, options) ==> string<a id="sec-5-2-9"></a>
@@ -558,7 +544,7 @@ Uploads the data in file at `localPath` to a new file on remote server at `remot
558
544
  // a part of a file was transferred
559
545
  }
560
546
  ```
561
-
547
+
562
548
  - **Warning:** There have been reports that some SFTP servers will not honour requests for non-default chunk sizes. This can result in data loss or corruption.
563
549
 
564
550
  2. Example Use
@@ -567,7 +553,7 @@ Uploads the data in file at `localPath` to a new file on remote server at `remot
567
553
  let localFile = '/path/to/file.txt';
568
554
  let remoteFile = '/path/to/remote/file.txt';
569
555
  let client = new Client();
570
-
556
+
571
557
  client.connect(config)
572
558
  .then(() => {
573
559
  client.fastPut(localFile, remoteFile);
@@ -591,7 +577,7 @@ Append the `input` data to an existing remote file. There is no integrity checki
591
577
  1. Options
592
578
 
593
579
  The following options are supported;
594
-
580
+
595
581
  ```javascript
596
582
  {
597
583
  flags: 'a', // w - write and a - append
@@ -600,7 +586,7 @@ Append the `input` data to an existing remote file. There is no integrity checki
600
586
  autoClose: true // automatically close the write stream when finished
601
587
  }
602
588
  ```
603
-
589
+
604
590
  The most common options to use are mode and encoding. The values shown above are the defaults. You do not have to set encoding to utf-8 for text files, null is fine for all file types. Generally, I would not attempt to append binary files.
605
591
 
606
592
  2. Example Use
@@ -608,7 +594,7 @@ Append the `input` data to an existing remote file. There is no integrity checki
608
594
  ```javascript
609
595
  let remotePath = '/path/to/remote/file.txt';
610
596
  let client = new Client();
611
-
597
+
612
598
  client.connect(config)
613
599
  .then(() => {
614
600
  return client.append(Buffer.from('Hello world'), remotePath);
@@ -633,7 +619,7 @@ Create a new directory. If the recursive flag is set to true, the method will cr
633
619
  ```javascript
634
620
  let remoteDir = '/path/to/new/dir';
635
621
  let client = new Client();
636
-
622
+
637
623
  client.connect(config)
638
624
  .then(() => {
639
625
  return client.mkdir(remoteDir, true);
@@ -660,7 +646,7 @@ Remove a directory. If removing a directory and recursive flag is set to `true`,
660
646
  ```javascript
661
647
  let remoteDir = '/path/to/remote/dir';
662
648
  let client = new Client();
663
-
649
+
664
650
  client.connect(config)
665
651
  .then(() => {
666
652
  return client.rmdir(remoteDir, true);
@@ -686,7 +672,7 @@ Delete a file on the remote server.
686
672
  ```javascript
687
673
  let remoteFile = '/path/to/remote/file.txt';
688
674
  let client = new Client();
689
-
675
+
690
676
  client.connect(config)
691
677
  .then(() => {
692
678
  return client.delete(remoteFile);
@@ -712,7 +698,7 @@ Rename a file or directory from `fromPath` to `toPath`. You must have the necess
712
698
  let from = '/remote/path/to/old.txt';
713
699
  let to = '/remote/path/to/new.txt';
714
700
  let client = new Client();
715
-
701
+
716
702
  client.connect(config)
717
703
  .then(() => {
718
704
  return client.rename(from, to);
@@ -762,7 +748,7 @@ Change the mode (read, write or execute permissions) of a remote file or directo
762
748
  let path = '/path/to/remote/file.txt';
763
749
  let newMode = 0o644; // rw-r-r
764
750
  let client = new Client();
765
-
751
+
766
752
  client.connect(config)
767
753
  .then(() => {
768
754
  return client.chmod(path, newMode);
@@ -803,28 +789,28 @@ The optionsl *filter* argument is a regular expression which can be used to sele
803
789
 
804
790
  ```javascript
805
791
  'use strict';
806
-
792
+
807
793
  // Example of using the uploadDir() method to upload a directory
808
794
  // to a remote SFTP server
809
-
795
+
810
796
  const path = require('path');
811
797
  const SftpClient = require('../src/index');
812
-
798
+
813
799
  const dotenvPath = path.join(__dirname, '..', '.env');
814
800
  require('dotenv').config({path: dotenvPath});
815
-
801
+
816
802
  const config = {
817
803
  host: process.env.SFTP_SERVER,
818
804
  username: process.env.SFTP_USER,
819
805
  password: process.env.SFTP_PASSWORD,
820
806
  port: process.env.SFTP_PORT || 22
821
807
  };
822
-
808
+
823
809
  async function main() {
824
810
  const client = new SftpClient('upload-test');
825
811
  const src = path.join(__dirname, '..', 'test', 'testData', 'upload-src');
826
812
  const dst = '/home/tim/upload-test';
827
-
813
+
828
814
  try {
829
815
  await client.connect(config);
830
816
  client.on('upload', info => {
@@ -836,7 +822,7 @@ The optionsl *filter* argument is a regular expression which can be used to sele
836
822
  client.end();
837
823
  }
838
824
  }
839
-
825
+
840
826
  main()
841
827
  .then(msg => {
842
828
  console.log(msg);
@@ -844,7 +830,7 @@ The optionsl *filter* argument is a regular expression which can be used to sele
844
830
  .catch(err => {
845
831
  console.log(`main error: ${err.message}`);
846
832
  });
847
-
833
+
848
834
  ```
849
835
 
850
836
  ### downloadDir(srcDir, dstDir, filter) ==> string<a id="sec-5-2-20"></a>
@@ -863,28 +849,28 @@ The optional *filter* argument is a regular expression which can be used to sele
863
849
 
864
850
  ```javascript
865
851
  'use strict';
866
-
852
+
867
853
  // Example of using the downloadDir() method to upload a directory
868
854
  // to a remote SFTP server
869
-
855
+
870
856
  const path = require('path');
871
857
  const SftpClient = require('../src/index');
872
-
858
+
873
859
  const dotenvPath = path.join(__dirname, '..', '.env');
874
860
  require('dotenv').config({path: dotenvPath});
875
-
861
+
876
862
  const config = {
877
863
  host: process.env.SFTP_SERVER,
878
864
  username: process.env.SFTP_USER,
879
865
  password: process.env.SFTP_PASSWORD,
880
866
  port: process.env.SFTP_PORT || 22
881
867
  };
882
-
868
+
883
869
  async function main() {
884
870
  const client = new SftpClient('upload-test');
885
871
  const dst = '/tmp';
886
872
  const src = '/home/tim/upload-test';
887
-
873
+
888
874
  try {
889
875
  await client.connect(config);
890
876
  client.on('download', info => {
@@ -896,7 +882,7 @@ The optional *filter* argument is a regular expression which can be used to sele
896
882
  client.end();
897
883
  }
898
884
  }
899
-
885
+
900
886
  main()
901
887
  .then(msg => {
902
888
  console.log(msg);
@@ -904,7 +890,7 @@ The optional *filter* argument is a regular expression which can be used to sele
904
890
  .catch(err => {
905
891
  console.log(`main error: ${err.message}`);
906
892
  });
907
-
893
+
908
894
  ```
909
895
 
910
896
  ### end() ==> boolean<a id="sec-5-2-21"></a>
@@ -915,7 +901,7 @@ Ends the current client session, releasing the client socket and associated reso
915
901
 
916
902
  ```javascript
917
903
  let client = new Client();
918
-
904
+
919
905
  client.connect(config)
920
906
  .then(() => {
921
907
  // do some sftp stuff
@@ -934,11 +920,13 @@ Although normally not required, you can add and remove custom listeners on the s
934
920
 
935
921
  - **error:** An error occurred. Calls listener with an error argument.
936
922
  - **end:** The socket has been disconnected. No argument.
937
- - **close:** The socket was closed. Boolean argument which is true when the socket was closed due to errors.
923
+ - **close:** The socket was closed.
938
924
 
939
925
  1. on(eventType, listener)
940
926
 
941
- Adds the specified listener to the specified event type. It the event type is `error`, the listener should accept 1 argument, which will be an Error object. If the event type is `close`, the listener should accept one argument of a boolean type, which will be true when the client connection was closed due to errors.
927
+ Adds the specified listener to the specified event type. It the event type is `error`, the listener should accept 1 argument, which will be an Error object. The event handlers for `end` and `close` events have no arguments.
928
+
929
+ The handlers will be added to the beginning of the listener's event handlers, so it will be called before any of the `ssh2-sftp-client` listeners.
942
930
 
943
931
  2. removeListener(eventType, listener)
944
932
 
@@ -948,23 +936,29 @@ Although normally not required, you can add and remove custom listeners on the s
948
936
 
949
937
  ## Server Capabilities<a id="sec-6-1"></a>
950
938
 
951
- All SFTP servers and platforms are not equal. Some facilities provided by `ssh2-sfto-client` either depend on capabilities of the remote server or the underlying capabilities of the remote server platform. As an example, consider `chmod()`. This command depends on a remote filesystem which implements the 'nix' concept of users and groups. The *win32* platform does not have the same concept of users and groups, so `chmod()` will not behave in the same way.
939
+ All SFTP servers and platforms are not equal. Some facilities provided by `ssh2-sftp-client` either depend on capabilities of the remote server or the underlying capabilities of the remote server platform. As an example, consider `chmod()`. This command depends on a remote filesystem which implements the 'nix' concept of users and groups. The *win32* platform does not have the same concept of users and groups, so `chmod()` will not behave in the same way.
952
940
 
953
941
  One way to determine whether an issue you are encountering is due to `ssh2-sftp-client` or due to the remote server or server platform is to use a simple CLI sftp program, such as openSSH's sftp command. If you observe the same behaviour using plain `sftp` on the command line, the issue is likely due to server or remote platform limitations. Note that you should not use a GUI sftp client, like `Filezilla` or `winSCP` as such GUI programs often attempt to hide these server and platform incompatibilities and will take additional steps to simulate missing functionality etc. You want to use a CLI program which does as little as possible.
954
942
 
955
- One way to determine whether an issue you are encountering is due to `ssh2-sftp-client` or due to the remote server or server platform is to use a simple CLI sftp program, such as openSSH's sftp command. If you observe the same behaviour using plain `sftp` on the command line, the issue is likely due to server or remote platform limitations. Note that you should not use a GUI sftp client, like `Filezilla` or `winSCP` as such GUI programs often attempt to hide these server and platform incompatibilities and will take additional steps to simulate missing functionality etc.
943
+ ## Promises, Events & Managing Exceptions<a id="sec-6-2"></a>
944
+
945
+ One of the challenges in providing a Promise based API over a module like SSH2, which is event based is how to ensure events are handled appropriately. The challenge is due to the synchronous nature of events. You cannot use `try/catch` for events because you have no way of knowing when the event might fire. For example, it could easily fire after your `try/catch` block as completed execution.
946
+
947
+ Things become even more complicated once you mix in Promises. When you define a promise, you have to methods which can be called to fulfil a promise, `resolve` and `reject`. Only one can be called - once you call `resolve`, you cannot call `reject` (well, you can call it, but it won't have any impact on the fulfilment status of the promise). The problem arises when an event, for exmaple an `error` event is fired either after you have resolved a promise or possibly in-between promises. If you don't catch the `error` event, your script will likely crash with an `uncaught exception` error.
956
948
 
957
- ## Promises & Events<a id="sec-6-2"></a>
949
+ To make matters worse, some servers, particularly servers running on a Windows platform, will raise multiple errors for the same error *event*. For example, when you attempt to connect with a bad username or password, you will get a `All authentication methods have failed` exception. However, under Windows, you will also get a `Connection reset by peer` exception. If we reject the connect promise based on the authentication failure exception, what do we do with the `reset by peer` exception? More critically, what will handle that exception given the promise has already been fulfilled and completed? To make matters worse, it seems that Windows based servers also raise an error event for *non-errors*. For example, when you call the `end()` method, the connection is closed. On windows, this also results in a *connection reset by peer* error. While it could be argued that the remote server resetting the connection after receiving a disconnect request is not an error, it doesn't change the fact that one is raised and we need to somehow deal with it.
958
950
 
959
- The reality of the current Node environment is that Promises and Events don't play nicely together. Part of the problem is that events are asynchronous in nature and can occur at any time. It is very difficult to ensure an event is captured inside a Promise and handled appropriately. More information can be found in the Node documentation for Events.
951
+ To handle this, `ssh2-sftp-client` implements a couple of strategies. Firstly, when you call one of the module's methods, it adds `error`, `end` and `close` event listeners which will call the `reject` moethod on the enclosing promise. It also keeps track of whether an error has been handled and if it has, it ignores any subsequent errors until the promise ends. Typically, the first error caught has the most relevant information and any subsequent error events are less critical or informative, so ignoring them has no negative impact. Provided one of the events is raised before the promise is fulfilled, these handlers will consume the event and deal with it appropriately.
960
952
 
961
- Node v12 has introduced some experimental features to make working with Events and Promises a little easier. At this stage, we are not using these features because they are experimental and because it would mean you cannot use this module with Node v10. Use of these features will likely be examined more closely once they become stable and non-experimental.
953
+ In testing, it was found that in some situations, particularly during connect operations, subsequent errors fired with a small delay. This prevents the errors from being handled by the event handlers associated with the connect promise. To deal with this, a small 500ms delay has been added to the connect() method, which effectively delays the removal of the event handlers until all events have been caught.
962
954
 
963
- So, what does this mean for this module? The `ssh2-sftp-client` module works hard to ensure things work as expected. In most cases, events are handled appropriately. However, there are some edge cases where events may not be handled and you may see an uncaught error exception. The most common place to see this is when you keep an SFTP connection open, but don't use it for some time. When the connection is open, but no methods are active (running), there are no error handlers defined. Should an error event be emitted (for exmaple, because the network connection has been lost), there is no handler and you will get an uncaught error exception.
955
+ The other area where additional events are fired is during the end() call. To deal with these events, the `end()` method setus up listeners which will simply ignore additional `error`, `end` and `close` events. It is assumed that once you have called `end()` you really only care about any main error which occurs and no longer care about other errors that may be raised as the connection is terminated.
964
956
 
965
- One way to handle this is to add your own error handler using the on() method. Note however, you need to be careful how many times your error handler is added. If you begin to see a warning about a possible memory leak, it is an indication your error handler is being added multiple times (Node will generate this warning if it finds more than 11 listeners attached to an event emitter).
957
+ In addition to the promise based event handlers, `ssh2-sftp-client` also implements global event handlers which will catch any `error`, `end` or `close` events. Essentially, these global handlers only reset the `sftp` property of the client object, effectively ensuring any subsequent calls are rejected and in the case of an error, send the error to the console.
966
958
 
967
- The other issue that can occur is that in some rare cases, the error message you get will be potentially misleading. For example, SFTP servers running on Windows appear to emit an *ECONNRESET* error in addition to the main error (for example, for failed authentication). This can result in an error which looks like a connection was reset by the remote host when in fact the real error was due to bad authentication (bad password or bad username). This situation can be made even worse by some platforms which deliberately hide the real error for security reasons e.g. does not report an error indicating a bad username because that information can be used to try and identify legitimate usernames. While this module attempts to provide meaningful error messages which can assist developers track down problems, it is a good idea to consider these errors with a grain of salt and verify the error when possible.
959
+ ### Adding Custom Handlers<a id="sec-6-2-1"></a>
960
+
961
+ While the above strategies appear to work for the majority of use cases, there are always going to be edge cases which require more flexible or powerful event handling. To support this, the `on()` and `removeListener()` methods are provided. Any event listener added using the `on()` method will be added at the beginning of the list of handlers for that event, ensuring it will be called before any global or promise local events. See the documentation for the `on()` method for details.
968
962
 
969
963
  ## Windows Based Servers<a id="sec-6-3"></a>
970
964
 
@@ -1113,6 +1107,8 @@ client.connect({
1113
1107
 
1114
1108
  Some users have encountered the error 'Timeout while waiting for handshake' or 'Handshake failed, no matching client->server ciphers. This is often due to the client not having the correct configuration for the transport layer algorithms used by ssh2. One of the connect options provided by the ssh2 module is `algorithm`, which is an object that allows you to explicitly set the key exchange, ciphers, hmac and compression algorithms as well as server host key used to establish the initial secure connection. See the SSH2 documentation for details. Getting these parameters correct usually resolves the issue.
1115
1109
 
1110
+ When encountering this type of problem, one worthwhile approach is to use openSSH's CLI sftp program with the `-v` switch to raise loggin levels. This will show you what algorithms the CLI is using. You can then use this information to match the names with the accepted algorithm names documented in the `ssh2` README to set the properties in the `algorithms` object.
1111
+
1116
1112
  ## How can I limit upload/download speed<a id="sec-7-6"></a>
1117
1113
 
1118
1114
  If you want to limit the amount of bandwidth used during upload/download of data, you can use a stream to limit throughput. The following example was provided by *kennylbj*. Note that there is a caveat that we must set the `autoClose` flag to false to avoid calling an extra `_read()` on a closed stream that may cause \_get Permission Denied error in ssh2-streams.
@@ -1150,6 +1146,14 @@ try {
1150
1146
  }
1151
1147
  ```
1152
1148
 
1149
+ ## Connection hangs or fails for larger files<a id="sec-7-7"></a>
1150
+
1151
+ This was contributed by Ladislav Jacho. Thanks.
1152
+
1153
+ A symptom of this issue is that you are able to upload small files, but uploading larger ones fail. You probably have an MTU/fragmentation problem. For each network interface on both client and server set the MTU to 576, e.g. `ifconfig eth0 mtu 576`. If that works, you need to find the largest MTU which will work for your network. An MTU which is too small will adversely affect throughput speed. A common value to use is an MTU of 1400.
1154
+
1155
+ For more explanation, see [issue #342](https://github.com/theophilusx/ssh2-sftp-client/issues/342).
1156
+
1153
1157
  # Examples<a id="sec-8"></a>
1154
1158
 
1155
1159
  I have started collecting example scripts in the example directory of the repository. These are mainly scripts I have put together in order to investigate issues or provide samples for users. They are not robust, lack adequate error handling and may contain errors. However, I think they are still useful for helping developers see how the module and API can be used.
@@ -1327,6 +1331,8 @@ Perhaps the best assistance is a minimal reproducible example of the issue. Once
1327
1331
 
1328
1332
  Pull requests are always welcomed. However, please ensure your changes pass all tests and if your adding a new feature, that tests for that feature are included. Likewise, for new features or enhancements, please include any relevant documentation updates.
1329
1333
 
1334
+ **Note**: The `README.md` file is generated from the `README.org` file. Therefore, any documentation updates or fixes need to be made to the `README.org` file. This file is *tangled* using `Emacs` org mode. If you don't use Emacs or org-mode, don't be too concerned. The org-mode syntax is straight-forward and similar to *markdown*. I will verify any updates to `README.org` and generate a new `README.md` when necessary. The main point to note is that any changes made directly to `README.md` will not persist and will be lost when a new version is generated, so don't modify that file.
1335
+
1330
1336
  This module will adopt a standard semantic versioning policy. Please indicate in your pull request what level of change it represents i.e.
1331
1337
 
1332
1338
  - **Major:** Change to API or major change in functionality which will require an increase in major version number.
@@ -1348,3 +1354,5 @@ Thanks to the following for their contributions -
1348
1354
  - **teenangst:** Contributed fix for error code 4 in stat() method
1349
1355
  - **kennylbj:** Contributed example of using a throttle stream to limit upload/download bandwidth.
1350
1356
  - **anton-erofeev:** Documentation fix
1357
+ - **Ladislav Jacho:** Contributed solution explanation for connections hanging when transferring larger files.
1358
+ - **Emma Milner:** Contributed fix for put() bug