fortify2-js
Version:
MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.
220 lines (218 loc) • 7.44 kB
JavaScript
/**
* Comparison Operations Module
* Handles secure string comparison operations
*/
/**
* Handles secure string comparison operations
*/
class ComparisonOperations {
/**
* Performs constant-time string comparison to prevent timing attacks
*/
static constantTimeEquals(str1, str2) {
const startTime = performance.now();
// Ensure both strings are the same length for constant-time comparison
const maxLength = Math.max(str1.length, str2.length);
// Pad shorter string with null characters
const paddedStr1 = str1.padEnd(maxLength, '\0');
const paddedStr2 = str2.padEnd(maxLength, '\0');
let result = 0;
// XOR each character - if strings are equal, result will remain 0
for (let i = 0; i < maxLength; i++) {
result |= paddedStr1.charCodeAt(i) ^ paddedStr2.charCodeAt(i);
}
const endTime = performance.now();
const isEqual = result === 0 && str1.length === str2.length;
return {
isEqual,
timeTaken: endTime - startTime,
constantTime: true,
};
}
/**
* Regular string comparison (faster but potentially vulnerable to timing attacks)
*/
static regularEquals(str1, str2) {
const startTime = performance.now();
const isEqual = str1 === str2;
const endTime = performance.now();
return {
isEqual,
timeTaken: endTime - startTime,
constantTime: false,
};
}
/**
* Case-insensitive constant-time comparison
*/
static constantTimeEqualsIgnoreCase(str1, str2) {
return this.constantTimeEquals(str1.toLowerCase(), str2.toLowerCase());
}
/**
* Compares strings lexicographically
*/
static compare(str1, str2) {
if (str1 < str2)
return -1;
if (str1 > str2)
return 1;
return 0;
}
/**
* Case-insensitive lexicographic comparison
*/
static compareIgnoreCase(str1, str2) {
return this.compare(str1.toLowerCase(), str2.toLowerCase());
}
/**
* Compares strings using locale-specific rules
*/
static localeCompare(str1, str2, locales, options) {
return str1.localeCompare(str2, locales, options);
}
/**
* Checks if two strings are similar within a threshold
*/
static isSimilar(str1, str2, threshold = 0.8) {
const similarity = this.calculateSimilarity(str1, str2);
return similarity >= threshold;
}
/**
* Calculates similarity between two strings using Levenshtein distance
*/
static calculateSimilarity(str1, str2) {
const distance = this.levenshteinDistance(str1, str2);
const maxLength = Math.max(str1.length, str2.length);
if (maxLength === 0)
return 1; // Both strings are empty
return 1 - (distance / maxLength);
}
/**
* Calculates Levenshtein distance between two strings
*/
static levenshteinDistance(str1, str2) {
const matrix = [];
// Initialize matrix
for (let i = 0; i <= str2.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= str1.length; j++) {
matrix[0][j] = j;
}
// Fill matrix
for (let i = 1; i <= str2.length; i++) {
for (let j = 1; j <= str1.length; j++) {
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
}
else {
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
matrix[i][j - 1] + 1, // insertion
matrix[i - 1][j] + 1 // deletion
);
}
}
}
return matrix[str2.length][str1.length];
}
/**
* Calculates Hamming distance (for strings of equal length)
*/
static hammingDistance(str1, str2) {
if (str1.length !== str2.length) {
throw new Error("Hamming distance requires strings of equal length");
}
let distance = 0;
for (let i = 0; i < str1.length; i++) {
if (str1.charAt(i) !== str2.charAt(i)) {
distance++;
}
}
return distance;
}
/**
* Calculates Jaro similarity
*/
static jaroSimilarity(str1, str2) {
if (str1.length === 0 && str2.length === 0)
return 1;
if (str1.length === 0 || str2.length === 0)
return 0;
const matchWindow = Math.floor(Math.max(str1.length, str2.length) / 2) - 1;
const str1Matches = new Array(str1.length).fill(false);
const str2Matches = new Array(str2.length).fill(false);
let matches = 0;
let transpositions = 0;
// Find matches
for (let i = 0; i < str1.length; i++) {
const start = Math.max(0, i - matchWindow);
const end = Math.min(i + matchWindow + 1, str2.length);
for (let j = start; j < end; j++) {
if (str2Matches[j] || str1.charAt(i) !== str2.charAt(j))
continue;
str1Matches[i] = true;
str2Matches[j] = true;
matches++;
break;
}
}
if (matches === 0)
return 0;
// Count transpositions
let k = 0;
for (let i = 0; i < str1.length; i++) {
if (!str1Matches[i])
continue;
while (!str2Matches[k])
k++;
if (str1.charAt(i) !== str2.charAt(k))
transpositions++;
k++;
}
return (matches / str1.length + matches / str2.length +
(matches - transpositions / 2) / matches) / 3;
}
/**
* Calculates Jaro-Winkler similarity
*/
static jaroWinklerSimilarity(str1, str2, prefixScale = 0.1) {
const jaroSim = this.jaroSimilarity(str1, str2);
if (jaroSim < 0.7)
return jaroSim;
// Calculate common prefix length (up to 4 characters)
let prefixLength = 0;
for (let i = 0; i < Math.min(str1.length, str2.length, 4); i++) {
if (str1.charAt(i) === str2.charAt(i)) {
prefixLength++;
}
else {
break;
}
}
return jaroSim + (prefixLength * prefixScale * (1 - jaroSim));
}
/**
* Performs fuzzy matching with multiple algorithms
*/
static fuzzyMatch(str1, str2, algorithm = 'levenshtein') {
switch (algorithm) {
case 'levenshtein':
return this.calculateSimilarity(str1, str2);
case 'jaro':
return this.jaroSimilarity(str1, str2);
case 'jaro-winkler':
return this.jaroWinklerSimilarity(str1, str2);
default:
throw new Error(`Unsupported fuzzy matching algorithm: ${algorithm}`);
}
}
/**
* Checks if strings match with a given tolerance
*/
static matchesWithTolerance(str1, str2, tolerance = 0.8, algorithm = 'levenshtein') {
const similarity = this.fuzzyMatch(str1, str2, algorithm);
return similarity >= tolerance;
}
}
export { ComparisonOperations };
//# sourceMappingURL=comparison-operations.js.map