Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / chokidar / lib / nodefs-handler.js
1 'use strict';
2
3 var fs = require('fs');
4 var sysPath = require('path');
5 var readdirp = require('readdirp');
6 var isBinaryPath = require('is-binary-path');
7
8 // fs.watch helpers
9
10 // object to hold per-process fs.watch instances
11 // (may be shared across chokidar FSWatcher instances)
12 var FsWatchInstances = Object.create(null);
13
14 // Private function: Instantiates the fs.watch interface
15
16 // * path       - string, path to be watched
17 // * options    - object, options to be passed to fs.watch
18 // * listener   - function, main event handler
19 // * errHandler - function, handler which emits info about errors
20 // * emitRaw    - function, handler which emits raw event data
21
22 // Returns new fsevents instance
23 function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
24   var handleEvent = function(rawEvent, evPath) {
25     listener(path);
26     emitRaw(rawEvent, evPath, {watchedPath: path});
27
28     // emit based on events occuring for files from a directory's watcher in
29     // case the file's watcher misses it (and rely on throttling to de-dupe)
30     if (evPath && path !== evPath) {
31       fsWatchBroadcast(
32         sysPath.resolve(path, evPath), 'listeners', sysPath.join(path, evPath)
33       );
34     }
35   };
36   try {
37     return fs.watch(path, options, handleEvent);
38   } catch (error) {
39     errHandler(error);
40   }
41 }
42
43 // Private function: Helper for passing fs.watch event data to a
44 // collection of listeners
45
46 // * fullPath   - string, absolute path bound to the fs.watch instance
47 // * type       - string, listener type
48 // * val[1..3]  - arguments to be passed to listeners
49
50 // Returns nothing
51 function fsWatchBroadcast(fullPath, type, val1, val2, val3) {
52   if (!FsWatchInstances[fullPath]) return;
53   FsWatchInstances[fullPath][type].forEach(function(listener) {
54     listener(val1, val2, val3);
55   });
56 }
57
58 // Private function: Instantiates the fs.watch interface or binds listeners
59 // to an existing one covering the same file system entry
60
61 // * path       - string, path to be watched
62 // * fullPath   - string, absolute path
63 // * options    - object, options to be passed to fs.watch
64 // * handlers   - object, container for event listener functions
65
66 // Returns close function
67 function setFsWatchListener(path, fullPath, options, handlers) {
68   var listener = handlers.listener;
69   var errHandler = handlers.errHandler;
70   var rawEmitter = handlers.rawEmitter;
71   var container = FsWatchInstances[fullPath];
72   var watcher;
73   if (!options.persistent) {
74     watcher = createFsWatchInstance(
75       path, options, listener, errHandler, rawEmitter
76     );
77     return watcher.close.bind(watcher);
78   }
79   if (!container) {
80     watcher = createFsWatchInstance(
81       path,
82       options,
83       fsWatchBroadcast.bind(null, fullPath, 'listeners'),
84       errHandler, // no need to use broadcast here
85       fsWatchBroadcast.bind(null, fullPath, 'rawEmitters')
86     );
87     if (!watcher) return;
88     var broadcastErr = fsWatchBroadcast.bind(null, fullPath, 'errHandlers');
89     watcher.on('error', function(error) {
90       // Workaround for https://github.com/joyent/node/issues/4337
91       if (process.platform === 'win32' && error.code === 'EPERM') {
92         fs.open(path, 'r', function(err, fd) {
93           if (fd) fs.close(fd);
94           if (!err) broadcastErr(error);
95         });
96       } else {
97         broadcastErr(error);
98       }
99     });
100     container = FsWatchInstances[fullPath] = {
101       listeners: [listener],
102       errHandlers: [errHandler],
103       rawEmitters: [rawEmitter],
104       watcher: watcher
105     };
106   } else {
107     container.listeners.push(listener);
108     container.errHandlers.push(errHandler);
109     container.rawEmitters.push(rawEmitter);
110   }
111   var listenerIndex = container.listeners.length - 1;
112
113   // removes this instance's listeners and closes the underlying fs.watch
114   // instance if there are no more listeners left
115   return function close() {
116     delete container.listeners[listenerIndex];
117     delete container.errHandlers[listenerIndex];
118     delete container.rawEmitters[listenerIndex];
119     if (!Object.keys(container.listeners).length) {
120       container.watcher.close();
121       delete FsWatchInstances[fullPath];
122     }
123   };
124 }
125
126 // fs.watchFile helpers
127
128 // object to hold per-process fs.watchFile instances
129 // (may be shared across chokidar FSWatcher instances)
130 var FsWatchFileInstances = Object.create(null);
131
132 // Private function: Instantiates the fs.watchFile interface or binds listeners
133 // to an existing one covering the same file system entry
134
135 // * path       - string, path to be watched
136 // * fullPath   - string, absolute path
137 // * options    - object, options to be passed to fs.watchFile
138 // * handlers   - object, container for event listener functions
139
140 // Returns close function
141 function setFsWatchFileListener(path, fullPath, options, handlers) {
142   var listener = handlers.listener;
143   var rawEmitter = handlers.rawEmitter;
144   var container = FsWatchFileInstances[fullPath];
145   var listeners = [];
146   var rawEmitters = [];
147   if (
148     container && (
149       container.options.persistent < options.persistent ||
150       container.options.interval > options.interval
151     )
152   ) {
153     // "Upgrade" the watcher to persistence or a quicker interval.
154     // This creates some unlikely edge case issues if the user mixes
155     // settings in a very weird way, but solving for those cases
156     // doesn't seem worthwhile for the added complexity.
157     listeners = container.listeners;
158     rawEmitters = container.rawEmitters;
159     fs.unwatchFile(fullPath);
160     container = false;
161   }
162   if (!container) {
163     listeners.push(listener);
164     rawEmitters.push(rawEmitter);
165     container = FsWatchFileInstances[fullPath] = {
166       listeners: listeners,
167       rawEmitters: rawEmitters,
168       options: options,
169       watcher: fs.watchFile(fullPath, options, function(curr, prev) {
170         container.rawEmitters.forEach(function(rawEmitter) {
171           rawEmitter('change', fullPath, {curr: curr, prev: prev});
172         });
173         var currmtime = curr.mtime.getTime();
174         if (curr.size !== prev.size || currmtime > prev.mtime.getTime() || currmtime === 0) {
175           container.listeners.forEach(function(listener) {
176             listener(path, curr);
177           });
178         }
179       })
180     };
181   } else {
182     container.listeners.push(listener);
183     container.rawEmitters.push(rawEmitter);
184   }
185   var listenerIndex = container.listeners.length - 1;
186
187   // removes this instance's listeners and closes the underlying fs.watchFile
188   // instance if there are no more listeners left
189   return function close() {
190     delete container.listeners[listenerIndex];
191     delete container.rawEmitters[listenerIndex];
192     if (!Object.keys(container.listeners).length) {
193       fs.unwatchFile(fullPath);
194       delete FsWatchFileInstances[fullPath];
195     }
196   };
197 }
198
199 // fake constructor for attaching nodefs-specific prototype methods that
200 // will be copied to FSWatcher's prototype
201 function NodeFsHandler() {}
202
203 // Private method: Watch file for changes with fs.watchFile or fs.watch.
204
205 // * path     - string, path to file or directory.
206 // * listener - function, to be executed on fs change.
207
208 // Returns close function for the watcher instance
209 NodeFsHandler.prototype._watchWithNodeFs =
210 function(path, listener) {
211   var directory = sysPath.dirname(path);
212   var basename = sysPath.basename(path);
213   var parent = this._getWatchedDir(directory);
214   parent.add(basename);
215   var absolutePath = sysPath.resolve(path);
216   var options = {persistent: this.options.persistent};
217   if (!listener) listener = Function.prototype; // empty function
218
219   var closer;
220   if (this.options.usePolling) {
221     options.interval = this.enableBinaryInterval && isBinaryPath(basename) ?
222       this.options.binaryInterval : this.options.interval;
223     closer = setFsWatchFileListener(path, absolutePath, options, {
224       listener: listener,
225       rawEmitter: this.emit.bind(this, 'raw')
226     });
227   } else {
228     closer = setFsWatchListener(path, absolutePath, options, {
229       listener: listener,
230       errHandler: this._handleError.bind(this),
231       rawEmitter: this.emit.bind(this, 'raw')
232     });
233   }
234   return closer;
235 };
236
237 // Private method: Watch a file and emit add event if warranted
238
239 // * file       - string, the file's path
240 // * stats      - object, result of fs.stat
241 // * initialAdd - boolean, was the file added at watch instantiation?
242 // * callback   - function, called when done processing as a newly seen file
243
244 // Returns close function for the watcher instance
245 NodeFsHandler.prototype._handleFile =
246 function(file, stats, initialAdd, callback) {
247   var dirname = sysPath.dirname(file);
248   var basename = sysPath.basename(file);
249   var parent = this._getWatchedDir(dirname);
250
251   // if the file is already being watched, do nothing
252   if (parent.has(basename)) return callback();
253
254   // kick off the watcher
255   var closer = this._watchWithNodeFs(file, function(path, newStats) {
256     if (!this._throttle('watch', file, 5)) return;
257     if (!newStats || newStats && newStats.mtime.getTime() === 0) {
258       fs.stat(file, function(error, newStats) {
259         // Fix issues where mtime is null but file is still present
260         if (error) {
261           this._remove(dirname, basename);
262         } else {
263           this._emit('change', file, newStats);
264         }
265       }.bind(this));
266     // add is about to be emitted if file not already tracked in parent
267     } else if (parent.has(basename)) {
268       this._emit('change', file, newStats);
269     }
270   }.bind(this));
271
272   // emit an add event if we're supposed to
273   if (!(initialAdd && this.options.ignoreInitial)) {
274     if (!this._throttle('add', file, 0)) return;
275     this._emit('add', file, stats);
276   }
277
278   if (callback) callback();
279   return closer;
280 };
281
282 // Private method: Handle symlinks encountered while reading a dir
283
284 // * entry      - object, entry object returned by readdirp
285 // * directory  - string, path of the directory being read
286 // * path       - string, path of this item
287 // * item       - string, basename of this item
288
289 // Returns true if no more processing is needed for this entry.
290 NodeFsHandler.prototype._handleSymlink =
291 function(entry, directory, path, item) {
292   var full = entry.fullPath;
293   var dir = this._getWatchedDir(directory);
294
295   if (!this.options.followSymlinks) {
296     // watch symlink directly (don't follow) and detect changes
297     this._readyCount++;
298     fs.realpath(path, function(error, linkPath) {
299       if (dir.has(item)) {
300         if (this._symlinkPaths[full] !== linkPath) {
301           this._symlinkPaths[full] = linkPath;
302           this._emit('change', path, entry.stat);
303         }
304       } else {
305         dir.add(item);
306         this._symlinkPaths[full] = linkPath;
307         this._emit('add', path, entry.stat);
308       }
309       this._emitReady();
310     }.bind(this));
311     return true;
312   }
313
314   // don't follow the same symlink more than once
315   if (this._symlinkPaths[full]) return true;
316   else this._symlinkPaths[full] = true;
317 };
318
319 // Private method: Read directory to add / remove files from `@watched` list
320 // and re-read it on change.
321
322 // * dir        - string, fs path.
323 // * stats      - object, result of fs.stat
324 // * initialAdd - boolean, was the file added at watch instantiation?
325 // * depth      - int, depth relative to user-supplied path
326 // * target     - string, child path actually targeted for watch
327 // * wh         - object, common watch helpers for this path
328 // * callback   - function, called when dir scan is complete
329
330 // Returns close function for the watcher instance
331 NodeFsHandler.prototype._handleDir =
332 function(dir, stats, initialAdd, depth, target, wh, callback) {
333   var parentDir = this._getWatchedDir(sysPath.dirname(dir));
334   var tracked = parentDir.has(sysPath.basename(dir));
335   if (!(initialAdd && this.options.ignoreInitial) && !target && !tracked) {
336     if (!wh.hasGlob || wh.globFilter(dir)) this._emit('addDir', dir, stats);
337   }
338
339   // ensure dir is tracked (harmless if redundant)
340   parentDir.add(sysPath.basename(dir));
341   this._getWatchedDir(dir);
342
343   var read = function(directory, initialAdd, done) {
344     // Normalize the directory name on Windows
345     directory = sysPath.join(directory, '');
346
347     if (!wh.hasGlob) {
348       var throttler = this._throttle('readdir', directory, 1000);
349       if (!throttler) return;
350     }
351
352     var previous = this._getWatchedDir(wh.path);
353     var current = [];
354
355     readdirp({
356       root: directory,
357       entryType: 'all',
358       fileFilter: wh.filterPath,
359       directoryFilter: wh.filterDir,
360       depth: 0,
361       lstat: true
362     }).on('data', function(entry) {
363       var item = entry.path;
364       var path = sysPath.join(directory, item);
365       current.push(item);
366
367       if (entry.stat.isSymbolicLink() &&
368         this._handleSymlink(entry, directory, path, item)) return;
369
370       // Files that present in current directory snapshot
371       // but absent in previous are added to watch list and
372       // emit `add` event.
373       if (item === target || !target && !previous.has(item)) {
374         this._readyCount++;
375
376         // ensure relativeness of path is preserved in case of watcher reuse
377         path = sysPath.join(dir, sysPath.relative(dir, path));
378
379         this._addToNodeFs(path, initialAdd, wh, depth + 1);
380       }
381     }.bind(this)).on('end', function() {
382       if (throttler) throttler.clear();
383       if (done) done();
384
385       // Files that absent in current directory snapshot
386       // but present in previous emit `remove` event
387       // and are removed from @watched[directory].
388       previous.children().filter(function(item) {
389         return item !== directory &&
390           current.indexOf(item) === -1 &&
391           // in case of intersecting globs;
392           // a path may have been filtered out of this readdir, but
393           // shouldn't be removed because it matches a different glob
394           (!wh.hasGlob || wh.filterPath({
395             fullPath: sysPath.resolve(directory, item)
396           }));
397       }).forEach(function(item) {
398         this._remove(directory, item);
399       }, this);
400     }.bind(this)).on('error', this._handleError.bind(this));
401   }.bind(this);
402
403   var closer;
404
405   if (this.options.depth == null || depth <= this.options.depth) {
406     if (!target) read(dir, initialAdd, callback);
407     closer = this._watchWithNodeFs(dir, function(dirPath, stats) {
408       // if current directory is removed, do nothing
409       if (stats && stats.mtime.getTime() === 0) return;
410
411       read(dirPath, false);
412     });
413   } else {
414     callback();
415   }
416   return closer;
417 };
418
419 // Private method: Handle added file, directory, or glob pattern.
420 // Delegates call to _handleFile / _handleDir after checks.
421
422 // * path       - string, path to file or directory.
423 // * initialAdd - boolean, was the file added at watch instantiation?
424 // * depth      - int, depth relative to user-supplied path
425 // * target     - string, child path actually targeted for watch
426 // * callback   - function, indicates whether the path was found or not
427
428 // Returns nothing
429 NodeFsHandler.prototype._addToNodeFs =
430 function(path, initialAdd, priorWh, depth, target, callback) {
431   if (!callback) callback = Function.prototype;
432   var ready = this._emitReady;
433   if (this._isIgnored(path) || this.closed) {
434     ready();
435     return callback(null, false);
436   }
437
438   var wh = this._getWatchHelpers(path, depth);
439   if (!wh.hasGlob && priorWh) {
440     wh.hasGlob = priorWh.hasGlob;
441     wh.globFilter = priorWh.globFilter;
442     wh.filterPath = priorWh.filterPath;
443     wh.filterDir = priorWh.filterDir;
444   }
445
446   // evaluate what is at the path we're being asked to watch
447   fs[wh.statMethod](wh.watchPath, function(error, stats) {
448     if (this._handleError(error)) return callback(null, path);
449     if (this._isIgnored(wh.watchPath, stats)) {
450       ready();
451       return callback(null, false);
452     }
453
454     var initDir = function(dir, target) {
455       return this._handleDir(dir, stats, initialAdd, depth, target, wh, ready);
456     }.bind(this);
457
458     var closer;
459     if (stats.isDirectory()) {
460       closer = initDir(wh.watchPath, target);
461     } else if (stats.isSymbolicLink()) {
462       var parent = sysPath.dirname(wh.watchPath);
463       this._getWatchedDir(parent).add(wh.watchPath);
464       this._emit('add', wh.watchPath, stats);
465       closer = initDir(parent, path);
466
467       // preserve this symlink's target path
468       fs.realpath(path, function(error, targetPath) {
469         this._symlinkPaths[sysPath.resolve(path)] = targetPath;
470         ready();
471       }.bind(this));
472     } else {
473       closer = this._handleFile(wh.watchPath, stats, initialAdd, ready);
474     }
475
476     if (closer) this._closers[path] = closer;
477     callback(null, false);
478   }.bind(this));
479 };
480
481 module.exports = NodeFsHandler;