<commit_before>/*!
 * Node - Shredfile
 *
 * A basic wrapped to the Unix 'shred' command. 
 *
 * Copyright(c) 2013 <NAME> <<EMAIL>>
 * MIT Licensed
 */

// Module dependencies.
var __ = require('underscore');
var fs = require('fs');
var spawn = require('child_process').spawn;
var path = require('path');

// ****************************************************************************
// Return a new Shredfile object.
// -----
// @param	Object 	options		Supplied to the Shredfile object for configuration
// @return	Function / Class
// @api 	Public
// ****************************************************************************
module.exports = function(options) {

	// ****************************************************************************
	// Shredfile class definition
	// -----
	// @param	Object	options		Key => Value pairs to override default settings
	// ****************************************************************************
	function Shredfile(options) {
		// Configuration Settings
		if(typeof options == 'undefined') options = {};
		this.settings = {};
		this.settings.shred_path = '/usr/bin/shred';
		this.settings.force = false;
		this.settings.iterations = 3;
		this.settings.bytes = null;
		this.settings.remove = true;
		this.settings.zero = true;
		this.settings.debug_mode = false;
		
		// Override settings by user configs
		this.settings = __.extend(this.settings,options);
		
		// Verify specified paths exists
		this.shred_path_exists = false;
		
		// REQUIRED: Make sure shred exists at specified location
		if(fs.existsSync(this.settings.shred_path)) {
			this.shred_path_exists = true;
		} else {
			if(this.settings.debug_mode)
				console.log("shredfile: shred could not be found at " + this.shred_path + "!");
		}
		
		// Build shred flags
		this.shred_flags = build_shred_flags(this.settings);
	}
	
	// ****************************************************************************
	// Securely removes a file.
	// -----
	// @param	String|Array	files		The path (string) or paths (array of string) to files that need to be shredded
	// @param	Function		status_cb	What to do as file is being shredded.
	// @param	Function		end_cb		What to do when file has been shredded.
	// ****************************************************************************
	Shredfile.prototype.shred = function(files,end_cb,status_cb) {
		if(this.settings.debug_mode) 
			console.log("shredfile: Shredding initiated.");
		
		var orig_files = files;
		if(typeof files == 'string')
			files = [files];
		
		if(!__.isArray(files) || files.length <= 0) {
			if(this.settings.debug_mode) {
				console.log("shredfile: No file(s) specified to shred!");
				console.log(typeof files);
				console.dir(files);
			}
			return end_cb('No file(s) specified to shred!',files);
		}
		
		// For storing file name being actively shredded
		var file = path.basename(files[0]); 
		
		// For storing parent directory of file being actively shredded
		var active_file_path = path.dirname(files[0]); 
		
		// Spawn the shred binary
		var options = __.union(this.shred_flags,files)
		var shred = spawn(this.settings.shred_path,options);
		if(this.settings.debug_mode === true)
			console.log('shredfile: Configured shred command: ' + this.settings.shred_path + ' ' + options.join(' '));
		
		var self = this;
		
		shred.stderr.on('data', function(data) {
			if(self.settings.debug_mode) {
				console.log('shredfile: stderr: ' + data);
			}
		});
		
		shred.on('close', function(code) {
			if(code === 0) {
				if(typeof end_cb == 'function')
					end_cb(null,files);
			} else {
				if(typeof end_cb == 'function')
					end_cb('Shredding completed with issues. Exit Code: ' + code, files);
			}
		});
		
		shred.stderr.on('data', function (data) {
			if(typeof status_cb == 'function') {
				var matches;
				var progress = 0;
				var rename = '';
				
				data = data.toString().replace(/(\r\n|\n|\r)/gm,"");
				var valid_info = new RegExp("^" + self.settings.shred_path);
				if(data.match(valid_info)) {
					if(matches = data.match(/(\/[^:]+)\: pass (\d+)\/(\d+)/)) {
						active_file_path = path.dirname(matches[1]);
						file = path.basename(matches[1]);
						var numerator = parseInt(matches[2]);
						var denominator = parseInt(matches[3]);
						if(denominator !== 0)
							progress = numerator / denominator;
						if(typeof status_cb == 'function')
							return status_cb('overwriting',progress,file,active_file_path);
					}
					matches = data.match(/renamed to (\/.*)$/);
					if(__.isArray(matches)) {
						rename = path.basename(matches[1]).trim();
						if(!rename.match(/^[0]+$/)) return;
						if(file.length > 0)
							progress = rename.length / file.length;
						if(typeof status_cb == 'function')
							return status_cb('renaming',progress,file,active_file_path);
					}
				}
			}
		});
	};
	
	return new Shredfile(options);
};

// *****************************************************************************
// Builds out the flags based on the configuration the user provided
// -----
// @param	Object	settings	The settings used to build the flags
// @return	String				The concatenated shred flags
// @api		Private
// *****************************************************************************
function build_shred_flags(settings) {
	var flags_array = ['-v'];
	
	// Change permissions to allow writing if necessary?
	if(settings.force === true) 
		flags_array.push('-f');
		
	// How many overwrite iterations?
	if(typeof settings.iterations == 'number') 
		flags_array.push('--iterations=' + settings.iterations);
		
	// How many bytes to shred
	if(settings.bytes && settings.bytes.match(/^\d+[KMG]?$/))
		flags_array.push('--size=' + settings.bytes);
	
	// Should we actually remove the file?
	if(settings.remove)
		flags_array.push('-u');
	
	// Add a final overwrite with zeros to hide shredding?
	if(settings.zero)
		flags_array.push('-z');
	
	// Build the String
	return flags_array;
	
}<commit_msg>Updated code to modern ES20xx code style.<commit_after>// Module dependencies.
const { existsSync } = require('fs');
const { spawn } = require('child_process');
const path = require('path');

/**
 * Builds out the flags based on the configuration the user provided
 * @private
 * @param {object} settings - The settings used to build the flags
 * @returns {string} The concatenated shred flags
 */
function buildShredFlags(settings) {
    const flags = ['-v'];

    // Change permissions to allow writing if necessary?
    if (settings.force === true) flags.push('-f');

    // How many overwrite iterations?
    if (typeof settings.iterations == 'number')
        flags.push(`--iterations=${settings.iterations}`);

    // How many bytes to shred
    if (settings.bytes && settings.bytes.match(/^\d+[KMG]?$/))
        flags.push(`--size=${settings.bytes}`);

    // Should we actually remove the file?
    if (settings.remove) flags.push('-u');

    // Add a final overwrite with zeros to hide shredding?
    if (settings.zero) flags.push('-z');

    // Build the String
    return flags;

}

class ShredFile {
    /**
    * This callback is displayed as part of the ShredFile class.
    *
    * @callback ShredFile~endCallback
    * @param {Error} err - A standard error message (null if no error)
    * @param {float} file - The original files parameter passed into this shred method.
    */

    /**
    * This callback is displayed as part of the ShredFile class.
    *
    * @callback ShredFile~statusCallback
    * @param {string} action - This will be either 'overwriting' or 'renaming'
    * @param {float} progress - The percentage of the specific action that is complete (ex. 0.66)
    * @param {string} file - File name of the file that is currently being acted upon
    * @param {string} activeFilePath - Full path to the file that is currently being acted upon
    */

    /**
     * ShredFile Class constructor.
     *
     * @param {object} settings - Settings to override the defaults.
     * @param {string} [settings.shredPath='/usr/bin/shred'] - The path to the `shred` binary.
     * @param {boolean} [settings.force=false] - If true, change permissions to allow writing if necessary
     * @param {number} [settings.iterations=3] - Set the number of times to overwrite the file
     * @param {number} [settings.bytes=null] - The number of bytes to shred
     * @param {boolean} [settings.remove=true] - If true, trucates and removes the file after overwriting
     * @param {boolean} [settings.zero=true] - If true, add a final overwrite with zeros to hide shredding
     * @param {boolean} [settings.debugMode=fakse] - If true, turns on debugging (logs to console)
     * @example
     * 
     * // Instantiat with all defaults
     * const shred = new ShredFile();
     * 
     * // Instantiate with some settings overrides
     * const shredder = new ShredFile({ iteractions: 5, debugMode: true });
     */
    constructor(settings) {
        const defaults = {
            shredPath: '/usr/bin/shred',
            force: false,
            iterations: 3,
            bytes: null,
            remove: true,
            zero: true,
            debugMode: false,
        }

        this.settings = { ...defaults, ...settings };

        // Verify specified paths exists
        this.shredPath_exists = false;

        // REQUIRED: Make sure shred exists at specified location
        if (existsSync(this.settings.shredPath)) {
            this.shredPath_exists = true;
        } else {
            if (this.settings.debugMode)
                console.log(`shredfile: shred could not be found at ${this.shredPath}!`);
        }

        // Build shred flags
        this.shredFlags = buildShredFlags(this.settings);
    }

    /**
     * Allows one to securely shred a file or a set of files.
     *
     * @public
     * @async
     * @param {string | Array} files - A path to a file or an array of paths to files
     * @param {ShredFile~statusCallback} [statusCb] - (optional) This will be called on each status change (renaming and each overwrite iteration)
     * @param {ShredFile~endCallback} [endCb] - (optional) This will be called when done.
     * @returns {Promise<object>} If no `endCb` is provided, a Promise will be returned.
     * @example
     * 
     * const shredder = new ShredFile();
     * 
     * // Do some shredding (Promise-style)
     * async function doSomeShredding() {
     *     // Shred a single file
     *     const result = await shredder.shred('/path/to/some/file');
     * 
     *     // Shred multiple files
     *     const result = await shredder.shred(['/path/to/first/file', '/path/to/second/file']);
     * 
     *     // Shred a file with notifications on progress
     *     const result = await shredder.shred('/another/file/to/shred', (action, progress, file, activeFilePath) => {
     *         console.log(`Currently ${action} ${activeFilePath} (${(progress * 100).floor()}%));
     *     });
     * }
     * 
     * // Do some shredding (callback-style)
     * function moreShredding(cb) {
     *     shredder.shred('/path/to/file/file', null, (err, file) => {
     *         if (err) {
     *             console.error(err);
     *             cb(err, null);
     *         }
     *         else {
     *             console.log('All Done!');
     *             cb(null, true);
     *         }
     *     });
     * }
     */
    async shred(files, statusCb, endCb) {
        const self = this;
        let hasCb = false;
        
        // Verify second param, if supplied, is a function
        if (endCb && typeof endCb !== 'function') {
            throw new Error('Invalid endCb provided to init method. Third paramter, if provided, must be a function!');
        } else if (endCb && typeof endCb === 'function') {
            hasCb = true;
        }

        return new Promisea(async (resolve, reject) => {
            const origFiles = files;
            if (typeof files === 'string') files = [files];

            if (!Array.isArray(files) || files.length <= 0) {
                if (this.settings.debugMode) {
                    console.log("shredfile: No file(s) specified to shred!", typeof files, files);
                }
                const err = new Error('No file(s) specified to shred!');
                return (hasCb ? endCb(err, null) : reject(err));
            }

            // For storing file name being actively shredded
            const file = path.basename(files[0]);

            // For storing parent directory of file being actively shredded
            const activeFilePath = path.dirname(files[0]);

            // Spawn the shred binary
            const options = Array.from(new Set(this.shredFlags.concat(files)));
            const shred = spawn(this.settings.shredPath, options);
            if (this.settings.debugMode === true)
                console.log('shredfile: Configured shred command: ' + this.settings.shredPath + ' ' + options.join(' '));


            // What to do if there's an error...
            shred.stderr.on('data', (data) => {
                if (self.settings.debugMode) console.log(`shredfile: stderr: ${data}`);
                if (hasCb) endCb(new Error(data), null);
                else reject(new Error(data));
            });

            // What to do if when the shredding is complete...
            shred.on('close', (code) => {
                if (code === 0) {
                    if (hasCb) endCb(null, files);
                    else resolve(files);
                } else {
                    const errorMsg = `Shredding completed with issues. Exit Code: ${code}`;
                    if (hasCb) endCb(new Error(errorMsg), files);
                    else reject(errorMsg);
                }
            });

            shred.stderr.on('data', (data) => {
                if (statusCb && typeof statusCb === 'function') {
                    let matches;
                    let progress = 0;
                    let rename = '';

                    data = data.toString().replace(/(\r\n|\n|\r)/gm, "");
                    const validInfo = new RegExp(`^${self.settings.shredPath}`);
                    if (validInfo.test(data)) {
                        if (matches = data.match(/(\/[^:]+)\: pass (\d+)\/(\d+)/)) {
                            activeFilePath = path.dirname(matches[1]);
                            file = path.basename(matches[1]);
                            const numerator = parseInt(matches[2]);
                            const denominator = parseInt(matches[3]);
                            if (denominator !== 0) progress = numerator / denominator;
                            if (statusCb && typeof statusCb === 'function')
                                return statusCb('overwriting', progress, file, activeFilePath);
                        }
                        matches = data.match(/renamed to (\/.*)$/);
                        if (Array.isArray(matches)) {
                            rename = path.basename(matches[1]).trim();
                            if (!/^[0]+$/.test(rename)) return;
                            if (file.length > 0) progress = rename.length / file.length;
                            if (statusCb && typeof statusCb === 'function')
                                return statusCb('renaming', progress, file, activeFilePath);
                        }
                    }
                }
            });
        });
    }
}

module.exports = new ShredFile;
{'fraction_non_alphanumeric': 0.16736889868412524, 'fraction_numerical': 0.0027224995138393727, 'mean_word_length': 2.3029329907942624, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3881395', 'n_tokens_mistral': 4748, 'n_tokens_neox': 4260, 'n_words': 1208}