<commit_before>/**
 * Node.js CAPTCHA library that produces and uses simple math questions to
 * ask the user
 */

var fs = require('fs');
var crypto = require('crypto');
var exec = require('child_process').exec;
var _ = require('underscore');

/**
 * Default options for the captcha include configuration for LaTeX and
 * dvipng.
 */
var default_options = {
	'tex'			: '/usr/bin/latex',
	'dvipng'		: '/usr/bin/dvipng',
	'fg'			: '#ffffff',
	'bg'			: 'Transparent',
	'bounding'		: 'tight',
	'resolution'	: 100,
	'path'			: '/tmp/math-captcha',
	'minOps'		: 3,
	'maxOps'		: 5,
	'values'		: [1, 2, 3, 4, 5, 6, 7, 8, 9]
};

/**
 * Creates a new instance of the captcha manager and generator, accepting a list of options.
 * Any unspecified options will be defaulted using the above object
 * @param options The list of options to set for this captcha instance
 */
function captcha(options) {
	this.parseOptions(_.extend({}, default_options, options));
	this.operators.push(new Operator(5, true, '$1 + $2', 2, function(a, b) { return a+b; }));
	this.operators.push(new Operator(5, false, '$1 - $2', 2, function(a, b) { return a-b; }));
	this.operators.push(new Operator(3, true, '$1 \\times $2', 2, function(a, b) { return a*b; }));
	this.operators.push(new Operator(3, false, '\\frac{$1}{$2}', 2, function(a, b) { return a/b; }));

	// Create instance data
	this.options = {};				//!< Options listing
	this.dvipngcmd = '';			//!< Command used to invoke dvipng to produce a PNG to send to the user
	this.texcmd = '';				//!< Command used to invoke LaTeX to produce a dvi file
	this.operators = [];			//!< Array of operators that we can build expressions from
	this.captchas = {};				//!< Map of captchas that are in the wild

}

// Add in member data for the captcha class
_.extend(captcha.prototype, {
	/**
	 * Generates a new random math problem, writes it to a png, saves the relevant information locally,
	 * and then passes a key to the given callback. This key can be used to get the png, check answers,
	 * and release the resources associated with the captcha image.
	 * @param success Callback to be called when the captcha image is ready, success(key)
	 * @param failure Callback to be called if there is a problem, failure(err)
	 */
	generate : function(success, failure) {
		var exp = this.generateExpression(this.options.minOps, this.options.maxOps, this.options.values);
		var latex = this.wrapLatex(this.latex([].concat(exp)));
		var answer = this.solve([].concat(exp));

		// Create information/tracking structure
		var hash = crypto.createHash('md5');
		hash.update(latex, 'utf8');
		var key = hash.digest('hex');
		var file = this.options.path + '/' + key;
		this.captchas[key] = {
			exp : exp,
			latex : latex,
			answer : answer,
			file : file + '.png'
		};

		// Write out latex file, with a nasty callback chain
		var that = this;
		var procopts = {
			encoding : 'utf8',
			env : process.env
		};

		fs.writeFile(file+'.tex', latex, function(err) {
			if (err) {
				that.captchas[key] = undefined;
				failure(err);
			}
			else {
				// Invoke tex
				exec(that.texcmd + ' ' + file+'.tex', procopts, function(err) {
					if (err !== null) {
						that.captchas[key] = undefined;
						failure(err);
					}
					else {
						exec(that.dvipngcmd + ' -o '+file+'.png ' + file + '.dvi', procopts, function(err) {
							if (err !== null) {
								that.captchas[key] = undefined;
								failure(err);
							}
							else {
								success(key);
							}
						});
					}
				});
			}
		});
	},

	/**
	 * Retrieves the image path for a given key, to be called after success is invoked through generate()
	 * @param key The key for which to retrieve the image path
	 * @return string The path to the image
	 */
	getImage : function(key) {
		if (this.captchas[key])
			return this.captchas[key].file;
		else
			return null;
	},

	/**
	 * Checks an answer for a given captcha against the key, with rounding to the given number of decimal
	 * places
	 * @param key The key to check for
	 * @param answer The answer to compare
	 * @param places The number of decimal places to round to
	 * @return Boolean True if the answer matches, false otherwise
	 */
	check : function(key, answer, places) {
		if (this.captchas[key]) {
			var shift = Math.pow(10, places);
			var rounded = Math.round(this.captchas[key].answer*shift);
			var adjusted = Math.round(answer*shift);
			return adjusted == rounded;
		}
		return false;
	},

	/**
	 * Cleans up the files generated for a specific key and removes the key from the list of captchas
	 * @param key The key to clean up
	 */
	cleanup : function(key) {
		var that = this;
		if (this.captchas[key]) {
			this.captchas[key] = undefined;
			exec('rm ' + _.map(['.tex', '.aux', '.dvi', '.png', '.log'], function(v) {
				return that.options.path + '/' + key + v;
			}));
		}
	},

	/**
	 * Parses an options object and saves it as instance data. This updates the commands used to print images
	 * @param options Object whose keys and values are the options for this captcha instance
	 */
	parseOptions : function(options) {
		this.texcmd = options.tex + ' -halt-on-error -output-directory='+options.path;
		this.dvipngcmd = options.dvipng + ' -fg "' + this.parseColor(options.fg) + '" -bg "' + this.parseColor(options.bg) + '" -T ' + options.bounding + ' -D ' + options.resolution;
		this.options = options;
	},

	/**
	 * Parses a color into the format expected by dvipng for fg or bg arguments
	 * @param color The color to parse as a string, in either hex or rgb format
	 * @return String the color as a string suitable for dvipng input
	 */
	parseColor : function(color) {
		if (color.match(/transparent/i))
			return color;

		var match = color.match(/rgb[\( ]?(\d{1,3}\.?\d*)[, ]+(\d{1,3}\.?\d*)[, ]+(\d{1,3}\.?\d*)\)?/);
		var red, green, blue;

		if (match) {<commit_msg>Added timer to automatically clean up captchas after 10 minutes<commit_after>/**
 * Node.js CAPTCHA library that produces and uses simple math questions to
 * ask the user
 */

var fs = require('fs');
var crypto = require('crypto');
var exec = require('child_process').exec;
var _ = require('underscore');

/**
 * Default options for the captcha include configuration for LaTeX and
 * dvipng.
 */
var default_options = {
	'tex'			: '/usr/bin/latex',
	'dvipng'		: '/usr/bin/dvipng',
	'fg'			: '#ffffff',
	'bg'			: 'Transparent',
	'bounding'		: 'tight',
	'resolution'	: 100,
	'path'			: '/tmp/math-captcha',
	'minOps'		: 3,
	'maxOps'		: 5,
	'values'		: [1, 2, 3, 4, 5, 6, 7, 8, 9],
	'cleanupTime'	: 600
};

/**
 * Creates a new instance of the captcha manager and generator, accepting a list of options.
 * Any unspecified options will be defaulted using the above object
 * @param options The list of options to set for this captcha instance
 */
function captcha(options) {
	this.parseOptions(_.extend({}, default_options, options));
	this.operators.push(new Operator(5, true, '$1 + $2', 2, function(a, b) { return a+b; }));
	this.operators.push(new Operator(5, false, '$1 - $2', 2, function(a, b) { return a-b; }));
	this.operators.push(new Operator(3, true, '$1 \\times $2', 2, function(a, b) { return a*b; }));
	this.operators.push(new Operator(3, false, '\\frac{$1}{$2}', 2, function(a, b) { return a/b; }));

	// Create instance data
	this.options = {};				//!< Options listing
	this.dvipngcmd = '';			//!< Command used to invoke dvipng to produce a PNG to send to the user
	this.texcmd = '';				//!< Command used to invoke LaTeX to produce a dvi file
	this.operators = [];			//!< Array of operators that we can build expressions from
	this.captchas = {};				//!< Map of captchas that are in the wild

}

// Add in member data for the captcha class
_.extend(captcha.prototype, {
	/**
	 * Generates a new random math problem, writes it to a png, saves the relevant information locally,
	 * and then passes a key to the given callback. This key can be used to get the png, check answers,
	 * and release the resources associated with the captcha image.
	 * @param success Callback to be called when the captcha image is ready, success(key)
	 * @param failure Callback to be called if there is a problem, failure(err)
	 */
	generate : function(success, failure) {
		var exp = this.generateExpression(this.options.minOps, this.options.maxOps, this.options.values);
		var latex = this.wrapLatex(this.latex([].concat(exp)));
		var answer = this.solve([].concat(exp));

		// Create information/tracking structure
		var hash = crypto.createHash('md5');
		hash.update(latex, 'utf8');
		var key = hash.digest('hex');
		var file = this.options.path + '/' + key;
		this.captchas[key] = {
			exp : exp,
			latex : latex,
			answer : answer,
			file : file + '.png'
		};

		// Write out latex file, with a nasty callback chain
		var that = this;
		var procopts = {
			encoding : 'utf8',
			env : process.env
		};

		fs.writeFile(file+'.tex', latex, function(err) {
			if (err) {
				that.captchas[key] = undefined;
				failure(err);
			}
			else {
				// Invoke tex
				exec(that.texcmd + ' ' + file+'.tex', procopts, function(err) {
					if (err !== null) {
						that.captchas[key] = undefined;
						failure(err);
					}
					else {
						exec(that.dvipngcmd + ' -o '+file+'.png ' + file + '.dvi', procopts, function(err) {
							if (err !== null) {
								that.captchas[key] = undefined;
								failure(err);
							}
							else {
								that.captchas[key].handler = setTimeout(_.bind(that.cleanup, that, key), that.options.cleanupTime*1000);
								success(key);
							}
						});
					}
				});
			}
		});
	},

	/**
	 * Retrieves the image path for a given key, to be called after success is invoked through generate()
	 * @param key The key for which to retrieve the image path
	 * @return string The path to the image
	 */
	getImage : function(key) {
		if (this.captchas[key])
			return this.captchas[key].file;
		else
			return null;
	},

	/**
	 * Checks an answer for a given captcha against the key, with rounding to the given number of decimal
	 * places
	 * @param key The key to check for
	 * @param answer The answer to compare
	 * @param places The number of decimal places to round to
	 * @return Boolean True if the answer matches, false otherwise
	 */
	check : function(key, answer, places) {
		if (this.captchas[key]) {
			var shift = Math.pow(10, places);
			var rounded = Math.round(this.captchas[key].answer*shift);
			var adjusted = Math.round(answer*shift);
			return adjusted == rounded;
		}
		return false;
	},

	/**
	 * Cleans up the files generated for a specific key and removes the key from the list of captchas
	 * @param key The key to clean up
	 */
	cleanup : function(key) {
		var that = this;
		if (this.captchas[key]) {
			clearTimeout(this.captchas[key].handler);
			this.captchas[key] = undefined;
			exec('rm ' + _.map(['.tex', '.aux', '.dvi', '.png', '.log'], function(v) {
				return that.options.path + '/' + key + v;
			}));
		}
	},

	/**
	 * Parses an options object and saves it as instance data. This updates the commands used to print images
	 * @param options Object whose keys and values are the options for this captcha instance
	 */
	parseOptions : function(options) {
		this.texcmd = options.tex + ' -halt-on-error -output-directory='+options.path;
		this.dvipngcmd = options.dvipng + ' -fg "' + this.parseColor(options.fg) + '" -bg "' + this.parseColor(options.bg) + '" -T ' + options.bounding + ' -D ' + options.resolution;
		this.options = options;
	},

	/**
	 * Parses a color into the format expected by dvipng for fg or bg arguments
	 * @param color The color to parse as a string, in either hex or rgb format
	 * @return String the color as a string suitable for dvipng input
	 */
	parseColor : function(color) {
		if (color.match(/transparent/i))
			return color;

		var match = color.match(/rgb[\( ]?(\d{1,3}\.?\d*)[, ]+(\d{1,3}\.?\d*)[, ]+(\d{1,3}\.?\d*)\)?/);
		var red, green, blue;

		if (match) {
{'fraction_non_alphanumeric': 0.1464255677039529, 'fraction_numerical': 0.007653490328006728, 'mean_word_length': 3.163515406162465, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 3, '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': '3549067', 'n_tokens_mistral': 4451, 'n_tokens_neox': 3862, 'n_words': 1301}