ssh2-sftp-client 7.0.0 → 7.0.4

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/src/utils.js CHANGED
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const path = require('path');
5
+
4
6
  const { errorCode } = require('./constants');
5
7
 
6
8
  /**
@@ -67,15 +69,19 @@ let tempListeners = [];
67
69
  */
68
70
  function errorListener(client, name, reject) {
69
71
  let fn = (err) => {
70
- if (!client.errorHandled) {
72
+ if (client.endCalled || client.errorHandled) {
73
+ client.debugMsg(`${name}: Ignoring handled error: ${err.message}`);
74
+ } else {
75
+ client.debugMsg(`${name}: Handling error: ${err.message}`);
71
76
  client.errorHandled = true;
72
77
  if (reject) {
78
+ client.debugMsg(`${name}: handled error with reject`);
73
79
  reject(fmtError(err, name, err.code));
74
80
  } else {
81
+ client.debugMsg(`${name}: handling error with throw`);
75
82
  throw fmtError(err, name, err.code);
76
83
  }
77
84
  }
78
- client.debugMsg(`Handled Error: ${err.message} ${err.code}`);
79
85
  };
80
86
  tempListeners.push(['error', fn]);
81
87
  return fn;
@@ -83,12 +89,17 @@ function errorListener(client, name, reject) {
83
89
 
84
90
  function endListener(client, name, reject) {
85
91
  let fn = function () {
86
- client.debugMsg(`Handled end event for ${name}`);
87
- if (!client.endCalled) {
92
+ if (client.endCalled || client.endHandled) {
93
+ client.debugMsg(`${name}: Ignoring expected end event`);
94
+ } else {
95
+ client.debugMsg(`${name}: Handling end event`);
88
96
  client.sftp = undefined;
97
+ client.endHandled = true;
89
98
  if (reject) {
99
+ client.debugMsg(`${name}: handling end event with reject'`);
90
100
  reject(fmtError('Unexpected end event raised', name));
91
101
  } else {
102
+ client.debugMsg(`${name}: handling end event with throw`);
92
103
  throw fmtError('Unexpected end event raised', name);
93
104
  }
94
105
  }
@@ -99,12 +110,17 @@ function endListener(client, name, reject) {
99
110
 
100
111
  function closeListener(client, name, reject) {
101
112
  let fn = function () {
102
- client.debugMsg(`handled close event for ${name}`);
103
- if (!client.endCalled) {
113
+ if (client.endCalled || client.closeHandled) {
114
+ client.debugMsg(`${name}: ignoring expected close event`);
115
+ } else {
116
+ client.debugMsg(`${name}: handling unexpected close event`);
104
117
  client.sftp = undefined;
118
+ client.closeHandled = true;
105
119
  if (reject) {
120
+ client.debugMsg(`${name}: handling close event with reject`);
106
121
  reject(fmtError('Unexpected close event raised', name));
107
122
  } else {
123
+ client.debugMsg(`${name}: handling close event with throw`);
108
124
  throw fmtError('Unexpected close event raised', name);
109
125
  }
110
126
  }
@@ -114,73 +130,165 @@ function closeListener(client, name, reject) {
114
130
  }
115
131
 
116
132
  function addTempListeners(obj, name, reject) {
117
- obj.debugMsg(`${name}: Adding end listener`);
133
+ obj.debugMsg(`${name}: Adding temp event listeners`);
118
134
  obj.client.prependListener('end', endListener(obj, name, reject));
119
- obj.debugMsg(`${name}: Adding close listener`);
120
135
  obj.client.prependListener('close', closeListener(obj, name, reject));
121
- obj.debugMsg(`${name}: Adding error listener`);
122
136
  obj.client.prependListener('error', errorListener(obj, name, reject));
123
137
  }
124
138
 
125
- function removeTempListeners(obj) {
139
+ function removeTempListeners(obj, name) {
140
+ obj.debugMsg(`${name}: Removing temp event listeners`);
126
141
  tempListeners.forEach(([e, fn]) => {
127
- obj.debugMsg(`${obj.clientName}: Removing ${e} listener`);
128
142
  obj.client.removeListener(e, fn);
129
143
  });
130
144
  tempListeners = [];
131
145
  }
132
146
 
133
147
  /**
134
- * @async
148
+ * Checks to verify local object exists. Returns a character string representing the type
149
+ * type of local object if it exists, false if it doesn't.
135
150
  *
136
- * Tests to see if a path identifies an existing item. Returns either
137
- * 'd' = directory, 'l' = sym link or '-' regular file if item exists. Returns
138
- * false if it does not
151
+ * Return codes: l = symbolic link
152
+ * - = regular file
153
+ * d = directory
154
+ * s = socket
139
155
  *
140
- * @param {String} localPath
141
- * @returns {Boolean | String}
156
+ * @param {string} filePath - path to local object
157
+ * @returns {string | boolean} returns a string for object type if it exists, false otherwise
142
158
  */
143
- function localExists(filePath, writeable = false) {
144
- return new Promise((resolve, reject) => {
145
- const fileModes = writeable
146
- ? fs.constants.F_OK | fs.constants.W_OK
147
- : fs.constants.F_OK | fs.constants.R_OK;
148
- fs.access(filePath, fileModes, (err) => {
149
- if (err) {
150
- if (err.code === 'ENOENT') {
151
- resolve(false);
152
- } else {
153
- reject(err);
154
- }
155
- } else {
156
- fs.stat(filePath, (err2, stats) => {
157
- if (err2) {
158
- reject(err);
159
- } else {
160
- if (stats.isDirectory()) {
161
- resolve('d');
162
- } else if (stats.isSymbolicLink()) {
163
- resolve('l');
164
- } else if (stats.isFile()) {
165
- resolve('-');
166
- } else {
167
- resolve(false);
168
- }
169
- }
170
- });
171
- }
172
- });
173
- });
159
+ function localExists(filePath) {
160
+ const stats = fs.statSync(filePath, { throwIfNoEntry: false });
161
+ if (!stats) {
162
+ return false;
163
+ } else if (stats.isDirectory()) {
164
+ return 'd';
165
+ } else if (stats.isFile()) {
166
+ return '-';
167
+ } else {
168
+ throw fmtError(
169
+ `Bad path: ${filePath}: target must be a file or directory`,
170
+ 'localExists',
171
+ errorCode.badPath
172
+ );
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Verify access to local object. Returns an object with properties for status, type,
178
+ * details and code.
179
+ *
180
+ * return object {
181
+ * status: true if exists and can be accessed, false otherwise
182
+ * type: type of object '-' = file, 'd' = dir, 'l' = link, 's' = socket
183
+ * details: 'access ok' if object can be accessed, 'not found' if
184
+ * object does not exist, 'permission denied' if access denied
185
+ * code: error code if object does not exist or permission denied
186
+ * }
187
+ *
188
+ * @param {string} filePath = path to local object
189
+ * @param {string} mode = access mode - either 'r' or 'w'. Defaults to 'r'
190
+ * @returns {Object} with properties status, type, details and code
191
+ */
192
+ function haveLocalAccess(filePath, mode = 'r') {
193
+ const accessMode =
194
+ fs.constants.F_OK | (mode === 'w') ? fs.constants.W_OK : fs.constants.R_OK;
195
+
196
+ try {
197
+ fs.accessSync(filePath, accessMode);
198
+ const type = localExists(filePath);
199
+ return {
200
+ status: true,
201
+ type: type,
202
+ details: 'access OK',
203
+ code: 0,
204
+ };
205
+ } catch (err) {
206
+ switch (err.errno) {
207
+ case -2:
208
+ return {
209
+ status: false,
210
+ type: null,
211
+ details: 'not exist',
212
+ code: -2,
213
+ };
214
+ case -13:
215
+ return {
216
+ status: false,
217
+ type: localExists(filePath),
218
+ details: 'permission denied',
219
+ code: -13,
220
+ };
221
+ case -20:
222
+ return {
223
+ status: false,
224
+ type: null,
225
+ details: 'parent not a directory',
226
+ };
227
+ default:
228
+ return {
229
+ status: false,
230
+ type: null,
231
+ details: err.message,
232
+ };
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Checks to verify the object specified by filePath can either be written to or created
239
+ * if it doens't already exist. If it does not exist, checks to see if the parent entry in the
240
+ * path is a directory and can be written to. Returns an object with the same format as the object
241
+ * returned by 'haveLocalAccess'.
242
+ *
243
+ * @param {string} filePath - path to object to be created or written t
244
+ * @returns {Object} Object with properties status, type, destils and code
245
+ */
246
+ function haveLocalCreate(filePath) {
247
+ const { status, details, type } = haveLocalAccess(filePath, 'w');
248
+ if (!status && details === 'permission denied') {
249
+ //throw new Error(`Bad path: ${filePath}: permission denied`);
250
+ return {
251
+ status,
252
+ details,
253
+ type,
254
+ };
255
+ } else if (!status) {
256
+ const dirPath = path.dirname(filePath);
257
+ const localCheck = haveLocalAccess(dirPath, 'w');
258
+ if (localCheck.status && localCheck.type !== 'd') {
259
+ //throw new Error(`Bad path: ${dirPath}: not a directory`);
260
+ return {
261
+ status: false,
262
+ details: `${dirPath}: not a directory`,
263
+ type: null,
264
+ };
265
+ } else if (!localCheck.status) {
266
+ //throw new Error(`Bad path: ${dirPath}: ${localCheck.details}`);
267
+ return {
268
+ status: localCheck.status,
269
+ details: `${dirPath}: ${localCheck.details}`,
270
+ type: null,
271
+ };
272
+ } else {
273
+ return {
274
+ status: true,
275
+ details: 'access OK',
276
+ type: null,
277
+ code: 0,
278
+ };
279
+ }
280
+ }
281
+ return { status, details, type };
174
282
  }
175
283
 
176
284
  async function normalizeRemotePath(client, aPath) {
177
285
  try {
178
286
  if (aPath.startsWith('..')) {
179
287
  let root = await client.realPath('..');
180
- return root + client.remotePathSep + aPath.substring(3);
288
+ return root + client.remotePathSep + aPath.slice(3);
181
289
  } else if (aPath.startsWith('.')) {
182
290
  let root = await client.realPath('.');
183
- return root + client.remotePathSep + aPath.substring(2);
291
+ return root + client.remotePathSep + aPath.slice(2);
184
292
  }
185
293
  return aPath;
186
294
  } catch (err) {
@@ -234,8 +342,10 @@ module.exports = {
234
342
  closeListener,
235
343
  addTempListeners,
236
344
  removeTempListeners,
237
- localExists,
345
+ haveLocalAccess,
346
+ haveLocalCreate,
238
347
  normalizeRemotePath,
348
+ localExists,
239
349
  haveConnection,
240
350
  sleep,
241
351
  };
package/CHANGELOG.org DELETED
@@ -1,232 +0,0 @@
1
- * Change Logging
2
-
3
- ** V7.0.0
4
- - New version based on new SSH2 version 1.1.0.
5
- - Expand option handling for get() and put() methods *Breaking Change*
6
- - Re-factored the retry code in the connect() method
7
- - Improve error reporting for adding/removing listeners
8
- - Extend localExists() method to also verify read or write access
9
-
10
- ** V6.0.1
11
- - Fix issue with connect retry not releasing 'ready' listeners
12
- - Add finally clauses to all promises to ensure temporary listeners are deleted
13
- - Add nyc module to report on code test coverage
14
- - Add additional utils tests to increase test coverage
15
- - Removed some dead code and unused utility functions to reduce download size
16
- - Cleanup tests and reduce inter-test dependencies
17
-
18
- ** V6.0.0.0
19
- - Update connection retry code to use the promise-retry module instead of
20
- plain rety module
21
- - Added optional filter argument for uploadDir/downlDir to select which files
22
- and directories are included
23
- - Added an optional boolean argument to delete to turn off raising an error
24
- when delete target does not exists
25
- - Reduced/simplified argument verification code to reduce package size and
26
- increase performance
27
- - Refactored handling of events and add default close and error listeners to
28
- catch connections closed abruptly without an error being raised.
29
-
30
- ** V5.3.2
31
- - Minor README typo fixes
32
- - Fix error in local file path checks (#294)
33
-
34
- ** V5.3.1
35
- - Fix bug in handling of relative local paths
36
- - Change handling of stream closures in ~get()~ and ~put()~ methods
37
-
38
- ** v5.3.0
39
- - Refine event handler management
40
- - Fix path processing for win32 based sftp servers
41
- - Update documentation
42
- ** v5.2.2
43
- - Bug fix release. Add error code 4 check to stat() method.
44
- - bump Mocha version for tests
45
-
46
- ** v5.2.1
47
- - Move some dependencies into dev-Dependencies
48
- ** v5.2.0
49
- - Add new method posixRename() which uses the openSSH POSIX rename extension.
50
- ** v5.1.3
51
- - Fix bug when writing to root directory and failure due to not being able to
52
- determine parent
53
- - Refactor some tests to eliminate need to have artificial delays between
54
- tests
55
- - Bumped some dependency versions to latest version
56
- ** v5.1.2
57
- - Added back global close handler
58
- - Added dumpListeners() method
59
-
60
- ** v5.1.1
61
- - Added separate close handlers to each method.
62
- - Added missing return statement in connect method
63
- - Added additional troubleshooting documentation for
64
- common errors.
65
-
66
- ** v5.1.0
67
- - Fix bug in checkRemotePath() relating to handling of badly
68
- specified paths (issue #213)
69
- - Added additional debugging support
70
- - Add missing test for valid connection in end() method.
71
- - Bump ssh2 version to v0.8.8
72
-
73
- ** v5.0.2
74
- - Fix bugs related to win32 platform and local tests for valid directories
75
- - Fix problem with parsing of file paths
76
-
77
- ** v5.0.1
78
- - Turn down error checking to be less stringent and handle situations
79
- where user does not have read permission on parent directory.
80
-
81
- ** v5.0.0
82
- - Added two new methods ~uploadDir()~ and ~downloadDir()~
83
- - Removed deprecated ~auxList()~ method
84
- - Improved error message consistency
85
- - Added additional error checking to enable more accurate and useful error
86
- messages.
87
- - Added default error handler to deal with event errors which fire outside of
88
- active SftpClient methods (i.e. connection unexpectedly reset by remote host).
89
- - Modified event handlers to ensure that only event handlers added by the
90
- module are removed by the module (users now responsible for removing any
91
- custom event handlers they add).
92
- - Module error handlers added using ~prependListener~ to ensure they are
93
- called before any additional custom handlers added by client code.
94
- - Any error events fired during an ~end()~ call are now ignored.
95
-
96
- ** v4.3.1
97
- - Updated end() method to resolve once close event fires
98
- - Added errorListener to error event in each promise to catch error events
99
- and reject the promise. This should resolve the issue of some error events
100
- causing uncaughtException erros and causing the process to exit.
101
-
102
- ** v4.3.0
103
- - Ensure errors include an err.code property and pass through the error code
104
- from the originating error
105
- - Change tests for error type to use ~error.code~ instead of matching on
106
- ~error.message~.
107
-
108
- ** v4.2.4
109
- - Bumped ssh2 to v0.8.6
110
- - Added exists() usage example to examples directory
111
- - Clarify documentation on get() method
112
- ** v4.2.3
113
- - Fix bug in ~exist()~ where tests on root directory returned false
114
- - Minor documentation fixes
115
- - Clean up mkdir example
116
-
117
- ** v4.2.2
118
- - Minor documentation fixes
119
- - Added additional examples in the ~example~ directory
120
-
121
- ** v4.2.1
122
- - Remove default close listener. changes in ssh2 API removed the utility of a
123
- default close listener
124
- - Fix path handling. Under mixed environments (where client platform and
125
- server platform were different i.e. one windows the other unix), path
126
- handling was broken due tot he use of path.join().
127
- - Ensure error messages include path details. Instead of errors such as "No
128
- such file" now report "No such file /path/to/missing/file" to help with
129
- debugging
130
-
131
- ** v4.2.0
132
- - Work-around for SSH2 =end= event bug
133
- - Added ability to set client name in constructor method
134
- - Added additional error checking to prevent ~connect()~ being called on
135
- already connected client
136
- - Added additional examples in =example= directory
137
-
138
- ** v4.1.0
139
- - move ~end()~ call to resolve into close hook
140
- - Prevent ~put()~ and ~get()~ from creating empty files in destination when
141
- unable to read source
142
- - Expand tests for operations when lacking required permissions
143
- - Add additional data checks for ~append()~
144
- - Verify file exists
145
- - Verify file is writeable
146
- - Verify file is a regular file
147
- - Fix handling of relative paths
148
- - Add ~realPath()~ method
149
- - Add ~cwd()~ method
150
-
151
- ** v4.0.4
152
- - Minor documentation fix
153
- - Fix return value from ~get()~
154
-
155
- ** v4.0.3
156
- - Fix bug in mkdir() relating to handling of relative paths
157
- - Modify exists() to always return 'd' if path is '.'
158
-
159
- ** v4.0.2
160
- - Fix some minor packaging issues
161
-
162
- ** v4.0.0
163
- - Remove support for node < 8.x
164
- - Fix connection retry feature
165
- - sftp connection object set to null when 'end' signal is raised
166
- - Removed 'connectMethod' argument from connect method.
167
- - Refined adding/removing of listeners in connect() and end() methods to enable
168
- errors to be adequately caught and reported.
169
- - Deprecate auxList() and add pattern/regexp filter option to list()
170
- - Refactored handling of event signals to provide better feedback to clients
171
- - Removed pointless 'permissions' property from objects returned by ~stat()~
172
- (same as mode property). Added additional properties describing the type of
173
- object.
174
- - Added the ~removeListener()~ method to compliment the existing ~on()~ method.
175
-
176
- ** Older Versions
177
- *** v2.5.2
178
- - Repository transferred to theophilusx
179
- - Fix error in package.json pointing to wrong repository
180
-
181
- *** v2.5.1
182
- - Apply 4 pull requests to address minor issues prior to transfer
183
-
184
- *** v2.5.0
185
- - ???
186
-
187
- *** v2.4.3
188
- - merge #108, #110
189
- - fix connect promise if connection ends
190
-
191
- *** v2.4.2
192
- - merge #105
193
- - fix windows path
194
-
195
- *** v2.4.1
196
- - merge pr #99, #100
197
- - bug fix
198
-
199
- *** v2.4.0
200
- - Requires node.js v7.5.0 or above.
201
- - merge pr #97, thanks for @theophilusx
202
- - Remove emitter.maxListener warnings
203
- - Upgraded ssh2 dependency from 0.5.5 to 0.6.1
204
- - Enhanced error messages to provide more context and to be more consistent
205
- - re-factored test
206
- - Added new 'exists' method and re-factored mkdir/rmdir
207
-
208
- *** v2.3.0
209
- - add: ~stat~ method
210
- - add ~fastGet~ and ~fastPut~ method.
211
- - fix: ~mkdir~ file exists decision logic
212
-
213
- *** v3.0.0 -- deprecate this version
214
- - change: ~sftp.get~ will return chunk not stream anymore
215
- - fix: get readable not emitting data events in node 10.0.0
216
-
217
- *** v2.1.1
218
- - add: event listener. [[https://github.com/jyu213/ssh2-sftp-client#Event][doc]]
219
- - add: ~get~ or ~put~ method add extra options [[https://github.com/jyu213/ssh2-sftp-client/pull/52][pr#52]]
220
-
221
- *** v2.0.1
222
- - add: ~chmod~ method [[https://github.com/jyu213/ssh2-sftp-client/pull/33][pr#33]]
223
- - update: upgrade ssh2 to V0.5.0 [[https://github.com/jyu213/ssh2-sftp-client/pull/30][pr#30]]
224
- - fix: get method stream error reject unwork [[https://github.com/jyu213/ssh2-sftp-client/issues/22][#22]]
225
- - fix: return Error object on promise rejection [[https://github.com/jyu213/ssh2-sftp-client/pull/20][pr#20]]
226
-
227
- *** v1.1.0
228
- - fix: add encoding control support for binary stream
229
-
230
- *** v1.0.5:
231
- - fix: multi image upload
232
- - change: remove ~this.client.sftp~ to ~connect~ function