w-ftp 1.0.0

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.
Files changed (50) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +54 -0
  4. package/.jsdoc +25 -0
  5. package/LICENSE +21 -0
  6. package/README.md +163 -0
  7. package/SECURITY.md +5 -0
  8. package/babel.config.js +14 -0
  9. package/docs/WFtp.mjs.html +665 -0
  10. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  11. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  12. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  13. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  14. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  15. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  16. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  17. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  18. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  19. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  20. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  21. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  22. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  23. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  24. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  25. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  26. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  27. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  28. package/docs/global.html +439 -0
  29. package/docs/index.html +81 -0
  30. package/docs/jsftp.js.html +925 -0
  31. package/docs/scripts/collapse.js +20 -0
  32. package/docs/scripts/linenumber.js +25 -0
  33. package/docs/scripts/nav.js +12 -0
  34. package/docs/scripts/polyfill.js +4 -0
  35. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  36. package/docs/scripts/prettify/lang-css.js +2 -0
  37. package/docs/scripts/prettify/prettify.js +28 -0
  38. package/docs/scripts/search.js +83 -0
  39. package/docs/styles/jsdoc.css +765 -0
  40. package/docs/styles/prettify.css +79 -0
  41. package/g-download.mjs +84 -0
  42. package/g-upload.mjs +65 -0
  43. package/package.json +30 -0
  44. package/src/WFtp.mjs +596 -0
  45. package/src/jsftp.js +856 -0
  46. package/test/all.test.mjs +10 -0
  47. package/toolg/addVersion.mjs +4 -0
  48. package/toolg/cleanFolder.mjs +4 -0
  49. package/toolg/gDistRollup.mjs +27 -0
  50. package/toolg/modifyReadme.mjs +4 -0
@@ -0,0 +1,925 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>jsftp.js - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
18
+ </head>
19
+ <body>
20
+
21
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
22
+ <label for="nav-trigger" class="navicon-button x">
23
+ <div class="navicon"></div>
24
+ </label>
25
+
26
+ <label for="nav-trigger" class="overlay"></label>
27
+
28
+ <nav >
29
+
30
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WFtp">WFtp</a></li></ul>
31
+ </nav>
32
+
33
+ <div id="main">
34
+
35
+ <h1 class="page-title">jsftp.js</h1>
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+ <section>
44
+ <article>
45
+ <pre class="prettyprint source linenums"><code>//fork from: https://github.com/sergi/jsftp
46
+
47
+ 'use strict'
48
+
49
+ const createConnection = require('net').createConnection
50
+ const EventEmitter = require('events').EventEmitter
51
+ const inherits = require('util').inherits
52
+ const stream = require('stream')
53
+ const fs = require('fs')
54
+ const combine = require('stream-combiner')
55
+
56
+ const ResponseParser = require('ftp-response-parser')
57
+ const ListingParser = require('parse-listing')
58
+ const once = require('once')
59
+ const nfc = require('unorm').nfc
60
+
61
+ const debug = require('debug')('jsftp:general')
62
+ const dbgCommand = require('debug')('jsftp:command')
63
+ const dbgResponse = require('debug')('jsftp:response')
64
+
65
+ const FTP_HOST = 'localhost'
66
+ const FTP_PORT = 21
67
+ const TIMEOUT = 10 * 60 * 1000
68
+ const IDLE_TIME = 30000
69
+ const NOOP = function() {}
70
+
71
+ const expectedMarks = {
72
+ marks: [125, 150],
73
+ ignore: 226
74
+ }
75
+
76
+ const RE_PASV = /([-\d]+,[-\d]+,[-\d]+,[-\d]+),([-\d]+),([-\d]+)/
77
+ const FTP_NEWLINE = /\r\n|\n/
78
+
79
+ function runCmd(name, ...params) {
80
+ let callback = NOOP
81
+ let completeCmd = name + ' '
82
+
83
+ if (typeof params[params.length - 1] === 'function') {
84
+ callback = params.pop()
85
+ }
86
+
87
+ completeCmd += params.join(' ')
88
+ this.execute(completeCmd.trim(), callback)
89
+ }
90
+
91
+ function Ftp(cfg) {
92
+ this.host = cfg.host || FTP_HOST
93
+ this.port = cfg.port || FTP_PORT
94
+ this.user = cfg.user || 'anonymous'
95
+ this.pass = cfg.pass || '@anonymous'
96
+ this.createSocket = cfg.createSocket
97
+ // True if the server doesn't support the `stat` command. Since listing a
98
+ // directory or retrieving file properties is quite a common operation, it is
99
+ // more efficient to avoid the round-trip to the server.
100
+ this.useList = cfg.useList || false
101
+
102
+ this.commandQueue = []
103
+
104
+ EventEmitter.call(this)
105
+
106
+ this.on('data', dbgResponse)
107
+ this.on('error', dbgResponse)
108
+
109
+ this._createSocket(this.port, this.host)
110
+ }
111
+
112
+ inherits(Ftp, EventEmitter)
113
+
114
+ // Generate generic methods from parameter names. they can easily be
115
+ // overriden if we need special behavior. they accept any parameters given,
116
+ // it is the responsibility of the user to validate the parameters.
117
+ Ftp.prototype.raw = function() {
118
+ runCmd.apply(this, arguments)
119
+ }
120
+
121
+ Ftp.prototype.reemit = function(event) {
122
+ return data => {
123
+ this.emit(event, data)
124
+ debug(`event:${event}`, data || {})
125
+ }
126
+ }
127
+
128
+ Ftp.prototype._createSocket = function(port, host, firstAction = NOOP) {
129
+ if (this.socket &amp;&amp; this.socket.destroy) {
130
+ this.socket.destroy()
131
+ }
132
+
133
+ if (this.resParser) {
134
+ this.resParser.end()
135
+ }
136
+ this.resParser = new ResponseParser()
137
+
138
+ this.authenticated = false
139
+ this.socket = this.createSocket
140
+ ? this.createSocket({ port, host }, firstAction)
141
+ : createConnection(port, host, firstAction)
142
+ this.socket.on('connect', this.reemit('connect'))
143
+ this.socket.on('timeout', this.reemit('timeout'))
144
+
145
+ this.pipeline = combine(this.socket, this.resParser)
146
+
147
+ this.pipeline.on('data', data => {
148
+ this.emit('data', data)
149
+ dbgResponse(data.text)
150
+ this.parseResponse(data)
151
+ })
152
+ this.pipeline.on('error', this.reemit('error'))
153
+ }
154
+
155
+ Ftp.prototype.parseResponse = function(response) {
156
+ if (this.commandQueue.length === 0) {
157
+ return
158
+ }
159
+ if ([220].indexOf(response.code) > -1) {
160
+ return
161
+ }
162
+
163
+ const next = this.commandQueue[0].callback
164
+ if (response.isMark) {
165
+ // If we receive a Mark and it is not expected, we ignore that command
166
+ if (
167
+ !next.expectsMark ||
168
+ next.expectsMark.marks.indexOf(response.code) === -1
169
+ ) {
170
+ return
171
+ }
172
+
173
+ // We might have to ignore the command that comes after the mark.
174
+ if (next.expectsMark.ignore) {
175
+ this.ignoreCmdCode = next.expectsMark.ignore
176
+ }
177
+ }
178
+
179
+ if (this.ignoreCmdCode === response.code) {
180
+ this.ignoreCmdCode = null
181
+ return
182
+ }
183
+
184
+ this.parse(response, this.commandQueue.shift())
185
+ }
186
+
187
+ /**
188
+ * Sends a new command to the server.
189
+ *
190
+ * @param {String} command Command to write in the FTP socket
191
+ */
192
+ Ftp.prototype.send = function(command) {
193
+ if (!command) {
194
+ return
195
+ }
196
+
197
+ dbgCommand(command)
198
+ this.pipeline.write(command + '\r\n')
199
+
200
+ dbgCommand(command)
201
+ }
202
+
203
+ Ftp.prototype.nextCmd = function() {
204
+ const cmd = this.commandQueue[0]
205
+ if (!this.inProgress &amp;&amp; cmd) {
206
+ this.send(cmd.action)
207
+ this.inProgress = true
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Check whether the ftp user is authenticated at the moment of the
213
+ * enqueing. ideally this should happen in the `push` method, just
214
+ * before writing to the socket, but that would be complicated,
215
+ * since we would have to 'unshift' the auth chain into the queue
216
+ * or play the raw auth commands (that is, without enqueuing in
217
+ * order to not mess up the queue order. ideally, that would be
218
+ * built into the queue object. all this explanation to justify a
219
+ * slight slopiness in the code flow.
220
+ *
221
+ * @param {string} action
222
+ * @param {function} callback
223
+ */
224
+ Ftp.prototype.execute = function(action, callback = NOOP) {
225
+ if (this.socket &amp;&amp; this.socket.writable) {
226
+ return this.runCommand({ action, callback })
227
+ }
228
+
229
+ this.authenticated = false
230
+ this._createSocket(this.port, this.host, () => {
231
+ this.runCommand({ action, callback })
232
+ })
233
+ }
234
+
235
+ Ftp.prototype.runCommand = function(cmd) {
236
+ if (this.authenticated || /^(feat|syst|user|pass)/.test(cmd.action)) {
237
+ this.commandQueue.push(cmd)
238
+ this.nextCmd()
239
+ return
240
+ }
241
+
242
+ this.getFeatures(() => {
243
+ this.auth(this.user, this.pass, () => {
244
+ this.commandQueue.push(cmd)
245
+ this.nextCmd()
246
+ })
247
+ })
248
+ }
249
+
250
+ /**
251
+ * Parse is called each time that a comand and a request are paired
252
+ * together. That is, each time that there is a round trip of actions
253
+ * between the client and the server.
254
+ *
255
+ * @param {Object} response Response from the server (contains text and code)
256
+ * @param {Array} command Contains the command executed and a callback (if any)
257
+ */
258
+ Ftp.prototype.parse = function(response, command) {
259
+ let err = null
260
+ if (response.isError) {
261
+ err = new Error(response.text || 'Unknown FTP error.')
262
+ err.code = response.code
263
+ }
264
+
265
+ this.inProgress = false
266
+ command.callback(err, response)
267
+ this.nextCmd()
268
+ }
269
+
270
+ Ftp.prototype.getPasvPort = function(text) {
271
+ const match = RE_PASV.exec(text)
272
+ if (!match) {
273
+ return null
274
+ }
275
+
276
+ let host = match[1].replace(/,/g, '.')
277
+ if (host === '127.0.0.1') {
278
+ host = this.host
279
+ }
280
+
281
+ return {
282
+ host,
283
+ port: (parseInt(match[2], 10) &amp; 255) * 256 + (parseInt(match[3], 10) &amp; 255)
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Returns true if the current server has the requested feature.
289
+ *
290
+ * @param {String} feature Feature to look for
291
+ * @return {Boolean} Whether the current server has the feature
292
+ */
293
+ Ftp.prototype.hasFeat = function(feature) {
294
+ return !!feature &amp;&amp; this.features.indexOf(feature.toLowerCase()) > -1
295
+ }
296
+
297
+ /**
298
+ * Returns an array of features supported by the current FTP server
299
+ *
300
+ * @param {String} features Server response for the 'FEAT' command
301
+ * @return {String[]} Array of feature names
302
+ */
303
+ Ftp.prototype._parseFeats = function(features) {
304
+ // Split and ignore header and footer
305
+ const featureLines = features.split(FTP_NEWLINE).slice(1, -1)
306
+ return featureLines
307
+ .map(feat => feat.trim().toLowerCase())
308
+ .filter(feat => !!feat)
309
+ }
310
+
311
+ // Below this point all the methods are action helpers for FTP that compose
312
+ // several actions in one command
313
+ Ftp.prototype.getFeatures = function(callback) {
314
+ if (this.features) {
315
+ return callback(null, this.features)
316
+ }
317
+
318
+ this.raw('feat', (err, response) => {
319
+ this.features = err ? [] : this._parseFeats(response.text)
320
+ this.raw('syst', (err, res) => {
321
+ if (!err &amp;&amp; res.code === 215) {
322
+ this.system = res.text.toLowerCase()
323
+ }
324
+
325
+ callback(null, this.features)
326
+ })
327
+ })
328
+ }
329
+
330
+ /**
331
+ * Authenticates the user.
332
+ *
333
+ * @param {String} user Username
334
+ * @param {String} pass Password
335
+ * @param {Function} callback Follow-up function.
336
+ */
337
+ Ftp.prototype.auth = function(user, pass, callback) {
338
+ if (this.authenticating === true) {
339
+ return callback(new Error('This client is already authenticating'))
340
+ }
341
+
342
+ if (typeof user !== 'string') {
343
+ user = this.user
344
+ }
345
+ if (typeof pass !== 'string') {
346
+ pass = this.pass
347
+ }
348
+
349
+ this.authenticating = true
350
+ this.raw('user', user, (err, res) => {
351
+ if (err || [230, 331, 332].indexOf(res.code) === -1) {
352
+ this.authenticating = false
353
+ callback(err)
354
+ return
355
+ }
356
+ this.raw('pass', pass, (err, res) => {
357
+ this.authenticating = false
358
+
359
+ if (err) {
360
+ callback(err)
361
+ }
362
+ else if ([230, 202].indexOf(res.code) > -1) {
363
+ this.authenticated = true
364
+ this.user = user
365
+ this.pass = pass
366
+ this.raw('type', 'I', () => {
367
+ callback(undefined, res)
368
+ })
369
+ }
370
+ else if (res.code === 332) {
371
+ this.raw('acct', '') // ACCT not really supported
372
+ }
373
+ })
374
+ })
375
+ }
376
+
377
+ Ftp.prototype.setType = function(type, callback) {
378
+ type = type.toUpperCase()
379
+ if (this.type === type) {
380
+ return callback()
381
+ }
382
+
383
+ this.raw('type', type, (err, data) => {
384
+ if (!err) {
385
+ this.type = type
386
+ }
387
+
388
+ callback(err, data)
389
+ })
390
+ }
391
+
392
+ /**
393
+ * Lists a folder's contents using a passive connection.
394
+ *
395
+ * @param {String} path Remote path for the file/folder to retrieve
396
+ * @param {Function} callback Function to call with errors or results
397
+ */
398
+ Ftp.prototype.list = function(path, callback) {
399
+ if (arguments.length === 1) {
400
+ callback = arguments[0]
401
+ path = ''
402
+ }
403
+
404
+ let listing = ''
405
+ callback = once(callback)
406
+
407
+ this.getPasvSocket((err, socket) => {
408
+ if (err) {
409
+ return callback(err)
410
+ }
411
+
412
+ socket.setEncoding('utf8')
413
+ socket.on('data', data => {
414
+ listing += data
415
+ })
416
+
417
+ this.pasvTimeout(socket, callback)
418
+
419
+ socket.once('close', err => {
420
+ if (err) {
421
+ return callback(err)
422
+ }
423
+ else if (!listing) {
424
+ // Some servers return empty string
425
+ return callback({
426
+ code: 451,
427
+ text: `Could not retrieve a file listing for ${path}.`,
428
+ isMark: false,
429
+ isError: true
430
+ })
431
+ }
432
+ callback(null, listing)
433
+ })
434
+ socket.once('error', callback)
435
+
436
+ function cmdCallback(err, res) {
437
+ if (err) {
438
+ return callback(err)
439
+ }
440
+
441
+ const isExpectedMark = expectedMarks.marks.some(
442
+ mark => mark === res.code
443
+ )
444
+
445
+ if (!isExpectedMark) {
446
+ callback(
447
+ new Error(
448
+ `Expected marks ${expectedMarks.toString()} instead of: ${res.text}`
449
+ )
450
+ )
451
+ }
452
+ }
453
+
454
+ cmdCallback.expectsMark = expectedMarks
455
+
456
+ this.execute(`list ${path || ''}`, cmdCallback)
457
+ })
458
+ }
459
+
460
+ Ftp.prototype.emitProgress = function(data) {
461
+ this.emit('progress', {
462
+ filename: data.filename,
463
+ action: data.action,
464
+ total: data.totalSize || 0,
465
+ transferred:
466
+ data.socket[data.action === 'get' ? 'bytesRead' : 'bytesWritten']
467
+ })
468
+ }
469
+
470
+ /**
471
+ * Depending on the number of parameters, returns the content of the specified
472
+ * file or directly saves a file into the specified destination. In the latter
473
+ * case, an optional callback can be provided, which will receive the error in
474
+ * case the operation was not successful.
475
+ *
476
+ * @param {String} remotePath File to be retrieved from the FTP server
477
+ * @param {Function|String} localPath Local path where we create the new file
478
+ * @param {Function} [callback] Gets called on either success or failure
479
+ */
480
+ Ftp.prototype.get = function(remotePath, localPath, callback = NOOP) {
481
+ let finalCallback
482
+ const typeofLocalPath = typeof localPath
483
+
484
+ if (typeofLocalPath === 'function') {
485
+ finalCallback = localPath
486
+ }
487
+ else if (typeofLocalPath === 'string') {
488
+ callback = once(callback)
489
+ finalCallback = (err, socket) => {
490
+ if (err) {
491
+ return callback(err)
492
+ }
493
+
494
+ const writeStream = fs.createWriteStream(localPath)
495
+ writeStream.on('error', callback)
496
+
497
+ socket.on('readable', () => {
498
+ this.emitProgress({
499
+ filename: remotePath,
500
+ action: 'get',
501
+ socket: socket
502
+ })
503
+ })
504
+
505
+ // This ensures that any expected outcome is handled. There is no
506
+ // danger of the callback being executed several times, because it is
507
+ // wrapped in `once`.
508
+ socket.on('error', callback)
509
+ socket.on('end', callback)
510
+ socket.on('close', callback)
511
+
512
+ socket.pipe(writeStream)
513
+ }
514
+ }
515
+
516
+ this.getGetSocket(remotePath, once(finalCallback))
517
+ }
518
+
519
+ /**
520
+ * Returns a socket for a get (RETR) on a path. The socket is ready to be
521
+ * streamed, but it is returned in a paused state. It is left to the user to
522
+ * resume it.
523
+ *
524
+ * @param {String} path Path to the file to be retrieved
525
+ * @param {Function} callback Function to call when finalized, with the socket
526
+ * as a parameter
527
+ */
528
+ Ftp.prototype.getGetSocket = function(path, callback) {
529
+ callback = once(callback)
530
+ this.getPasvSocket((err, socket) => {
531
+ if (err) {
532
+ return cmdCallback(err)
533
+ }
534
+
535
+ socket.on('error', err => {
536
+ if (err.code === 'ECONNREFUSED') {
537
+ err.msg = 'Probably trying a PASV operation while one is in progress'
538
+ }
539
+ cmdCallback(err)
540
+ })
541
+
542
+ this.pasvTimeout(socket, cmdCallback)
543
+ socket.pause()
544
+
545
+ function cmdCallback(err, res) {
546
+ if (err) {
547
+ if (socket) {
548
+ // close the socket since it won't be used
549
+ socket.destroy()
550
+ }
551
+ return callback(err)
552
+ }
553
+
554
+ if (!socket) {
555
+ return callback(new Error('Error when retrieving PASV socket'))
556
+ }
557
+
558
+ if (res.code === 125 || res.code === 150) {
559
+ return callback(null, socket)
560
+ }
561
+
562
+ // close the socket since it won't be used
563
+ socket.destroy()
564
+
565
+ return callback(new Error('Unexpected command ' + res.text))
566
+ }
567
+
568
+ cmdCallback.expectsMark = expectedMarks
569
+ this.execute('retr ' + path, cmdCallback)
570
+ })
571
+ }
572
+
573
+ /**
574
+ * Uploads contents on a FTP server. The `from` parameter can be a Buffer or the
575
+ * path for a local file to be uploaded.
576
+ *
577
+ * @param {String|Buffer} from Contents to be uploaded.
578
+ * @param {String} destination path for the remote destination.
579
+ * @param {Function} callback Function to execute on error or success.
580
+ */
581
+ Ftp.prototype.put = function(from, destination, callback) {
582
+ const putReadable = (from, to, totalSize) => {
583
+ from.on('readable', () => {
584
+ this.emitProgress({
585
+ filename: to,
586
+ action: 'put',
587
+ socket: from,
588
+ totalSize
589
+ })
590
+ })
591
+
592
+ this.getPutSocket(from, to, callback)
593
+ }
594
+
595
+ if (from instanceof Buffer) {
596
+ // this.getPutSocket(from, destination, callback)
597
+ // let self = this
598
+ this.getPutSocket(from, destination, (err, socket) => {
599
+ if (err) {
600
+ return callback(new Error(err))
601
+ }
602
+ let pointer = 0
603
+ let SLICE = 1024 * 1024
604
+ let done = false
605
+ let newBuf
606
+
607
+ newBuf = from.slice(pointer, pointer + SLICE)
608
+ pointer += SLICE
609
+ callback(null, newBuf)
610
+ socket.write(from.slice(0, SLICE))
611
+
612
+ //check
613
+ if (newBuf.length === from.length) {
614
+ socket.end()
615
+ callback(null, [])
616
+ }
617
+
618
+ // socket.on('error', (msg) => {
619
+ // console.log('socket err', msg)
620
+ // })
621
+ // socket.on('end', (msg) => {
622
+ // console.log('socket end', msg)
623
+ // })
624
+ // socket.on('close', (msg) => {
625
+ // console.log('socket close', msg)
626
+ // })
627
+
628
+ socket.on('drain', () => {
629
+ console.log('drain')
630
+ if (from.length > pointer + SLICE) {
631
+ newBuf = from.slice(pointer, pointer + SLICE)
632
+ pointer += SLICE
633
+ }
634
+ else {
635
+ newBuf = from.slice(pointer)
636
+ done = true
637
+ // console.log('done', done)
638
+ }
639
+ callback(null, newBuf)
640
+ socket.write(newBuf)
641
+
642
+ // self.emit('progress', {
643
+ // filename: 'BUFFER',
644
+ // action: 'put',
645
+ // transferred: pointer,
646
+ // total: from.length
647
+ // })
648
+
649
+ //check
650
+ if (done) {
651
+ socket.end()
652
+ callback(null, [])
653
+ }
654
+
655
+ })
656
+
657
+ })
658
+
659
+ }
660
+ else if (typeof from === 'string') {
661
+ fs.stat(from, (err, stats) => {
662
+ if (err &amp;&amp; err.code === 'ENOENT') {
663
+ return callback(new Error('Local file doesn\'t exist.'))
664
+ }
665
+
666
+ if (stats.isDirectory()) {
667
+ return callback(new Error('Local path cannot be a directory'))
668
+ }
669
+
670
+ const totalSize = err ? 0 : stats.size
671
+ putReadable(fs.createReadStream(from), destination, totalSize)
672
+ })
673
+ }
674
+ else if (from instanceof stream.Readable) {
675
+ putReadable(from, destination, 0)
676
+ }
677
+ else {
678
+ callback(
679
+ new Error('Expected `from` parameter to be a Buffer, Stream, or a String')
680
+ )
681
+ }
682
+ }
683
+
684
+ Ftp.prototype.getPutSocket = function(from, path, next) {
685
+ next = once(next || NOOP)
686
+
687
+ this.getPasvSocket((err, socket) => {
688
+ if (err) {
689
+ if (socket) {
690
+ // close the socket since it won't be used
691
+ socket.destroy()
692
+ }
693
+ return next(err)
694
+ }
695
+
696
+ socket.on('close', next)
697
+ socket.on('error', next)
698
+
699
+ // socket.on('error', (msg) => {
700
+ // console.log('s error', msg)
701
+ // })
702
+ // socket.on('end', (msg) => {
703
+ // console.log('s end', msg)
704
+ // })
705
+ // socket.on('close', (msg) => {
706
+ // console.log('s close', msg)
707
+ // })
708
+ // socket.on('data', (msg) => {
709
+ // console.log('s data', msg)
710
+ // })
711
+
712
+ const callback = once((err, res) => {
713
+ if (err) {
714
+ if (socket) {
715
+ // close the socket since it won't be used
716
+ socket.destroy()
717
+ }
718
+ return next(err)
719
+ }
720
+
721
+ // Mark 150 indicates that the 'STOR' socket is ready to receive data.
722
+ // Anything else is not relevant.
723
+ if (res.code === 125 || res.code === 150) {
724
+ this.pasvTimeout(socket, next)
725
+ if (from instanceof Buffer) {
726
+ next(null, socket)
727
+ // socket.end(from)
728
+ }
729
+ else if (from instanceof stream.Readable) {
730
+ next(null, socket)
731
+ from.pipe(socket)
732
+ // //resume
733
+ // socket.resume()
734
+ }
735
+ }
736
+ else {
737
+ if (socket) {
738
+ // close the socket since it won't be used
739
+ socket.destroy()
740
+ }
741
+ return next(new Error('Unexpected command ' + res.text))
742
+ }
743
+ })
744
+
745
+ callback.expectsMark = expectedMarks
746
+
747
+ this.execute(`stor ${path}`, callback)
748
+ })
749
+ }
750
+
751
+ Ftp.prototype.pasvTimeout = function(socket, callback) {
752
+ socket.once('timeout', () => {
753
+ debug('PASV socket timeout')
754
+ this.emit('timeout')
755
+ socket.end()
756
+ callback(new Error('Passive socket timeout'))
757
+ })
758
+ }
759
+
760
+ Ftp.prototype.getPasvSocket = function(callback = NOOP) {
761
+ callback = once(callback)
762
+
763
+ this.execute('pasv', (err, res) => {
764
+ if (err) {
765
+ return callback(err)
766
+ }
767
+
768
+ const options = this.getPasvPort(res.text)
769
+ if (!options) {
770
+ return callback(new Error('Bad passive host/port combination'))
771
+ }
772
+
773
+ const socket = (this._pasvSocket = this.createSocket
774
+ ? this.createSocket(options)
775
+ : createConnection(options))
776
+ socket.setTimeout(this.timeout || TIMEOUT)
777
+ socket.once('close', () => {
778
+ this._pasvSocket = undefined
779
+ })
780
+
781
+ callback(null, socket)
782
+ })
783
+ }
784
+
785
+ /**
786
+ * Provides information about files. It lists a directory contents or
787
+ * a single file and yields an array of file objects. The file objects
788
+ * contain several properties. The main difference between this method and
789
+ * 'list' or 'stat' is that it returns objects with the file properties
790
+ * already parsed.
791
+ *
792
+ * Example of file object:
793
+ *
794
+ * {
795
+ * name: 'README.txt',
796
+ * type: 0,
797
+ * time: 996052680000,
798
+ * size: '2582',
799
+ * owner: 'sergi',
800
+ * group: 'staff',
801
+ * userPermissions: { read: true, write: true, exec: false },
802
+ * groupPermissions: { read: true, write: false, exec: false },
803
+ * otherPermissions: { read: true, write: false, exec: false }
804
+ * }
805
+ *
806
+ * The constants used in the object are defined in ftpParser.js
807
+ *
808
+ * @param {String} filePath Path to the file or directory to list
809
+ * @param {Function} callback Function to call with the proper data when
810
+ * the listing is finished.
811
+ */
812
+ Ftp.prototype.ls = function(filePath, callback) {
813
+ function entriesToList(err, entries) {
814
+ if (err) {
815
+ return callback(err)
816
+ }
817
+
818
+ ListingParser.parseFtpEntries(entries.text || entries, (err, files) => {
819
+ if (err) {
820
+ return callback(err)
821
+ }
822
+
823
+ files.forEach(file => {
824
+ // Normalize UTF8 doing canonical decomposition, followed by
825
+ // canonical Composition
826
+ file.name = nfc(file.name)
827
+ })
828
+ callback(null, files)
829
+ })
830
+ }
831
+
832
+ if (this.useList) {
833
+ this.list(filePath, entriesToList)
834
+ }
835
+ else {
836
+ this.raw('stat', filePath, (err, data) => {
837
+ // We might be connected to a server that doesn't support the
838
+ // 'STAT' command, which is set as default. We use 'LIST' instead,
839
+ // and we set the variable `useList` to true, to avoid extra round
840
+ // trips to the server to check.
841
+ const errored = err &amp;&amp; (err.code === 502 || err.code === 500)
842
+ const isHummingbird =
843
+ this.system &amp;&amp; this.system.indexOf('hummingbird') > -1
844
+ if (errored || isHummingbird) {
845
+ // Not sure if the 'hummingbird' system check ^^^ is still
846
+ // necessary. If they support any standards, the 500 error
847
+ // should have us covered. Let's leave it for now.
848
+ this.useList = true
849
+ this.list(filePath, entriesToList)
850
+ }
851
+ else {
852
+ entriesToList(err, data)
853
+ }
854
+ })
855
+ }
856
+ }
857
+
858
+ Ftp.prototype.rename = function(from, to, callback) {
859
+ this.raw('rnfr', from, err => {
860
+ if (err) {
861
+ return callback(err)
862
+ }
863
+ this.raw('rnto', to, callback)
864
+ })
865
+ }
866
+
867
+ Ftp.prototype.keepAlive = function(wait) {
868
+ if (this._keepAliveInterval) {
869
+ clearInterval(this._keepAliveInterval)
870
+ }
871
+
872
+ this._keepAliveInterval = setInterval(
873
+ this.raw.bind(this, 'noop'),
874
+ wait || IDLE_TIME
875
+ )
876
+ }
877
+
878
+ Ftp.prototype.destroy = function() {
879
+ if (this._keepAliveInterval) {
880
+ clearInterval(this._keepAliveInterval)
881
+ }
882
+
883
+ if (this.socket &amp;&amp; this.socket.writable) {
884
+ this.socket.end()
885
+ }
886
+
887
+ if (this._pasvSocket &amp;&amp; this._pasvSocket.writable) {
888
+ this._pasvSocket.end()
889
+ }
890
+
891
+ this.resParser.end()
892
+
893
+ this.socket = undefined
894
+ this._pasvSocket = undefined
895
+
896
+ this.features = null
897
+ this.authenticated = false
898
+ }
899
+
900
+ module.exports = Ftp
901
+ </code></pre>
902
+ </article>
903
+ </section>
904
+
905
+
906
+
907
+
908
+
909
+
910
+ </div>
911
+
912
+ <br class="clear">
913
+
914
+ <footer>
915
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.10</a> on Sat Jul 23 2022 13:03:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
916
+ </footer>
917
+
918
+ <script>prettyPrint();</script>
919
+ <script src="scripts/polyfill.js"></script>
920
+ <script src="scripts/linenumber.js"></script>
921
+
922
+
923
+
924
+ </body>
925
+ </html>