| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 | /*! * Tmp * * Copyright (c) 2011-2017 KARASZI Istvan <github@spam.raszi.hu> * * MIT Licensed *//* * Module dependencies. */const fs = require('fs');const path = require('path');const crypto = require('crypto');const osTmpDir = require('os-tmpdir');const _c = process.binding('constants');/* * The working inner variables. */const  /**   * The temporary directory.   * @type {string}   */  tmpDir = osTmpDir(),  // the random characters to choose from  RANDOM_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',  TEMPLATE_PATTERN = /XXXXXX/,  DEFAULT_TRIES = 3,  CREATE_FLAGS = (_c.O_CREAT || _c.fs.O_CREAT) | (_c.O_EXCL || _c.fs.O_EXCL) | (_c.O_RDWR || _c.fs.O_RDWR),  EBADF = _c.EBADF || _c.os.errno.EBADF,  ENOENT = _c.ENOENT || _c.os.errno.ENOENT,  DIR_MODE = 448 /* 0o700 */,  FILE_MODE = 384 /* 0o600 */,  // this will hold the objects need to be removed on exit  _removeObjects = [];var  _gracefulCleanup = false,  _uncaughtException = false;/** * Random name generator based on crypto. * Adapted from http://blog.tompawlak.org/how-to-generate-random-values-nodejs-javascript * * @param {number} howMany * @returns {string} the generated random name * @private */function _randomChars(howMany) {  var    value = [],    rnd = null;  // make sure that we do not fail because we ran out of entropy  try {    rnd = crypto.randomBytes(howMany);  } catch (e) {    rnd = crypto.pseudoRandomBytes(howMany);  }  for (var i = 0; i < howMany; i++) {    value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]);  }  return value.join('');}/** * Checks whether the `obj` parameter is defined or not. * * @param {Object} obj * @returns {boolean} true if the object is undefined * @private */function _isUndefined(obj) {  return typeof obj === 'undefined';}/** * Parses the function arguments. * * This function helps to have optional arguments. * * @param {(Options|Function)} options * @param {Function} callback * @returns {Array} parsed arguments * @private */function _parseArguments(options, callback) {  if (typeof options == 'function') {    return [callback || {}, options];  }  if (_isUndefined(options)) {    return [{}, callback];  }  return [options, callback];}/** * Generates a new temporary name. * * @param {Object} opts * @returns {string} the new random name according to opts * @private */function _generateTmpName(opts) {  if (opts.name) {    return path.join(opts.dir || tmpDir, opts.name);  }  // mkstemps like template  if (opts.template) {    return opts.template.replace(TEMPLATE_PATTERN, _randomChars(6));  }  // prefix and postfix  const name = [    opts.prefix || 'tmp-',    process.pid,    _randomChars(12),    opts.postfix || ''  ].join('');  return path.join(opts.dir || tmpDir, name);}/** * Gets a temporary file name. * * @param {(Options|tmpNameCallback)} options options or callback * @param {?tmpNameCallback} callback the callback function */function tmpName(options, callback) {  var    args = _parseArguments(options, callback),    opts = args[0],    cb = args[1],    tries = opts.name ? 1 : opts.tries || DEFAULT_TRIES;  if (isNaN(tries) || tries < 0)    return cb(new Error('Invalid tries'));  if (opts.template && !opts.template.match(TEMPLATE_PATTERN))    return cb(new Error('Invalid template provided'));  (function _getUniqueName() {    const name = _generateTmpName(opts);    // check whether the path exists then retry if needed    fs.stat(name, function (err) {      if (!err) {        if (tries-- > 0) return _getUniqueName();        return cb(new Error('Could not get a unique tmp filename, max tries reached ' + name));      }      cb(null, name);    });  }());}/** * Synchronous version of tmpName. * * @param {Object} options * @returns {string} the generated random name * @throws {Error} if the options are invalid or could not generate a filename */function tmpNameSync(options) {  var    args = _parseArguments(options),    opts = args[0],    tries = opts.name ? 1 : opts.tries || DEFAULT_TRIES;  if (isNaN(tries) || tries < 0)    throw new Error('Invalid tries');  if (opts.template && !opts.template.match(TEMPLATE_PATTERN))    throw new Error('Invalid template provided');  do {    const name = _generateTmpName(opts);    try {      fs.statSync(name);    } catch (e) {      return name;    }  } while (tries-- > 0);  throw new Error('Could not get a unique tmp filename, max tries reached');}/** * Creates and opens a temporary file. * * @param {(Options|fileCallback)} options the config options or the callback function * @param {?fileCallback} callback */function file(options, callback) {  var    args = _parseArguments(options, callback),    opts = args[0],    cb = args[1];  opts.postfix = (_isUndefined(opts.postfix)) ? '.tmp' : opts.postfix;  // gets a temporary filename  tmpName(opts, function _tmpNameCreated(err, name) {    if (err) return cb(err);    // create and open the file    fs.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err, fd) {      if (err) return cb(err);      if (opts.discardDescriptor) {        return fs.close(fd, function _discardCallback(err) {          if (err) {            // Low probability, and the file exists, so this could be            // ignored.  If it isn't we certainly need to unlink the            // file, and if that fails too its error is more            // important.            try {              fs.unlinkSync(name);            } catch (e) {              if (!isENOENT(e)) {                err = e;              }            }            return cb(err);          }          cb(null, name, undefined, _prepareTmpFileRemoveCallback(name, -1, opts));        });      }      if (opts.detachDescriptor) {        return cb(null, name, fd, _prepareTmpFileRemoveCallback(name, -1, opts));      }      cb(null, name, fd, _prepareTmpFileRemoveCallback(name, fd, opts));    });  });}/** * Synchronous version of file. * * @param {Options} options * @returns {FileSyncObject} object consists of name, fd and removeCallback * @throws {Error} if cannot create a file */function fileSync(options) {  var    args = _parseArguments(options),    opts = args[0];  opts.postfix = opts.postfix || '.tmp';  const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor;  const name = tmpNameSync(opts);  var fd = fs.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE);  if (opts.discardDescriptor) {    fs.closeSync(fd);     fd = undefined;  }  return {    name: name,    fd: fd,    removeCallback: _prepareTmpFileRemoveCallback(name, discardOrDetachDescriptor ? -1 : fd, opts)  };}/** * Removes files and folders in a directory recursively. * * @param {string} root * @private */function _rmdirRecursiveSync(root) {  const dirs = [root];  do {    var      dir = dirs.pop(),      deferred = false,      files = fs.readdirSync(dir);    for (var i = 0, length = files.length; i < length; i++) {      var        file = path.join(dir, files[i]),        stat = fs.lstatSync(file); // lstat so we don't recurse into symlinked directories      if (stat.isDirectory()) {        if (!deferred) {          deferred = true;          dirs.push(dir);        }        dirs.push(file);      } else {        fs.unlinkSync(file);      }    }    if (!deferred) {      fs.rmdirSync(dir);    }  } while (dirs.length !== 0);}/** * Creates a temporary directory. * * @param {(Options|dirCallback)} options the options or the callback function * @param {?dirCallback} callback */function dir(options, callback) {  var    args = _parseArguments(options, callback),    opts = args[0],    cb = args[1];  // gets a temporary filename  tmpName(opts, function _tmpNameCreated(err, name) {    if (err) return cb(err);    // create the directory    fs.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err) {      if (err) return cb(err);      cb(null, name, _prepareTmpDirRemoveCallback(name, opts));    });  });}/** * Synchronous version of dir. * * @param {Options} options * @returns {DirSyncObject} object consists of name and removeCallback * @throws {Error} if it cannot create a directory */function dirSync(options) {  var    args = _parseArguments(options),    opts = args[0];  const name = tmpNameSync(opts);  fs.mkdirSync(name, opts.mode || DIR_MODE);  return {    name: name,    removeCallback: _prepareTmpDirRemoveCallback(name, opts)  };}/** * Prepares the callback for removal of the temporary file. * * @param {string} name the path of the file * @param {number} fd file descriptor * @param {Object} opts * @returns {fileCallback} * @private */function _prepareTmpFileRemoveCallback(name, fd, opts) {  const removeCallback = _prepareRemoveCallback(function _removeCallback(fdPath) {    try {      if (0 <= fdPath[0]) {        fs.closeSync(fdPath[0]);      }    }    catch (e) {      // under some node/windows related circumstances, a temporary file      // may have not be created as expected or the file was already closed      // by the user, in which case we will simply ignore the error      if (!isEBADF(e) && !isENOENT(e)) {        // reraise any unanticipated error        throw e;      }    }    try {      fs.unlinkSync(fdPath[1]);    }    catch (e) {      if (!isENOENT(e)) {        // reraise any unanticipated error        throw e;      }    }  }, [fd, name]);  if (!opts.keep) {    _removeObjects.unshift(removeCallback);  }  return removeCallback;}/** * Prepares the callback for removal of the temporary directory. * * @param {string} name * @param {Object} opts * @returns {Function} the callback * @private */function _prepareTmpDirRemoveCallback(name, opts) {  const removeFunction = opts.unsafeCleanup ? _rmdirRecursiveSync : fs.rmdirSync.bind(fs);  const removeCallback = _prepareRemoveCallback(removeFunction, name);  if (!opts.keep) {    _removeObjects.unshift(removeCallback);  }  return removeCallback;}/** * Creates a guarded function wrapping the removeFunction call. * * @param {Function} removeFunction * @param {Object} arg * @returns {Function} * @private */function _prepareRemoveCallback(removeFunction, arg) {  var called = false;  return function _cleanupCallback(next) {    if (!called) {      const index = _removeObjects.indexOf(_cleanupCallback);      if (index >= 0) {        _removeObjects.splice(index, 1);      }      called = true;      removeFunction(arg);    }    if (next) next(null);  };}/** * The garbage collector. * * @private */function _garbageCollector() {  if (_uncaughtException && !_gracefulCleanup) {    return;  }  // the function being called removes itself from _removeObjects,  // loop until _removeObjects is empty  while (_removeObjects.length) {    try {      _removeObjects[0].call(null);    } catch (e) {      // already removed?    }  }}/** * Helper for testing against EBADF to compensate changes made to Node 7.x under Windows. */function isEBADF(error) {  return isExpectedError(error, -EBADF, 'EBADF');}/** * Helper for testing against ENOENT to compensate changes made to Node 7.x under Windows. */function isENOENT(error) {  return isExpectedError(error, -ENOENT, 'ENOENT');}/** * Helper to determine whether the expected error code matches the actual code and errno, * which will differ between the supported node versions. * * - Node >= 7.0: *   error.code {String} *   error.errno {String|Number} any numerical value will be negated * * - Node >= 6.0 < 7.0: *   error.code {String} *   error.errno {Number} negated * * - Node >= 4.0 < 6.0: introduces SystemError *   error.code {String} *   error.errno {Number} negated * * - Node >= 0.10 < 4.0: *   error.code {Number} negated *   error.errno n/a */function isExpectedError(error, code, errno) {  return error.code == code || error.code == errno;}/** * Sets the graceful cleanup. * * Also removes the created files and directories when an uncaught exception occurs. */function setGracefulCleanup() {  _gracefulCleanup = true;}const version = process.versions.node.split('.').map(function (value) {  return parseInt(value, 10);});if (version[0] === 0 && (version[1] < 9 || version[1] === 9 && version[2] < 5)) {  process.addListener('uncaughtException', function _uncaughtExceptionThrown(err) {    _uncaughtException = true;    _garbageCollector();    throw err;  });}process.addListener('exit', function _exit(code) {  if (code) _uncaughtException = true;  _garbageCollector();});/** * Configuration options. * * @typedef {Object} Options * @property {?number} tries the number of tries before give up the name generation * @property {?string} template the "mkstemp" like filename template * @property {?string} name fix name * @property {?string} dir the tmp directory to use * @property {?string} prefix prefix for the generated name * @property {?string} postfix postfix for the generated name *//** * @typedef {Object} FileSyncObject * @property {string} name the name of the file * @property {string} fd the file descriptor * @property {fileCallback} removeCallback the callback function to remove the file *//** * @typedef {Object} DirSyncObject * @property {string} name the name of the directory * @property {fileCallback} removeCallback the callback function to remove the directory *//** * @callback tmpNameCallback * @param {?Error} err the error object if anything goes wrong * @param {string} name the temporary file name *//** * @callback fileCallback * @param {?Error} err the error object if anything goes wrong * @param {string} name the temporary file name * @param {number} fd the file descriptor * @param {cleanupCallback} fn the cleanup callback function *//** * @callback dirCallback * @param {?Error} err the error object if anything goes wrong * @param {string} name the temporary file name * @param {cleanupCallback} fn the cleanup callback function *//** * Removes the temporary created file or directory. * * @callback cleanupCallback * @param {simpleCallback} [next] function to call after entry was removed *//** * Callback function for function composition. * @see {@link https://github.com/raszi/node-tmp/issues/57|raszi/node-tmp#57} * * @callback simpleCallback */// exporting all the needed methodsmodule.exports.tmpdir = tmpDir;module.exports.dir = dir;module.exports.dirSync = dirSync;module.exports.file = file;module.exports.fileSync = fileSync;module.exports.tmpName = tmpName;module.exports.tmpNameSync = tmpNameSync;module.exports.setGracefulCleanup = setGracefulCleanup;
 |