import { injectable, inject } from 'inversify'; import { ServiceIdentifiers } from '../../../container/ServiceIdentifiers'; import { ICustomNodeGroup } from '../../../interfaces/custom-nodes/ICustomNodeGroup'; import { IEncodedValue } from '../../../interfaces/node-transformers/IEncodedValue'; import { IOptions } from '../../../interfaces/options/IOptions'; import { IStorage } from '../../../interfaces/storages/IStorage'; import { StringArrayEncoding } from '../../../enums/StringArrayEncoding'; import { AbstractReplacer } from './AbstractReplacer'; import { CryptUtils } from '../../../utils/CryptUtils'; import { RandomGeneratorUtils } from '../../../utils/RandomGeneratorUtils'; import { Utils } from '../../../utils/Utils'; @injectable() export class StringLiteralReplacer extends AbstractReplacer { /** * @type {number} */ private static readonly minimumLengthForStringArray: number = 3; /** * @type {IStorage} */ private readonly customNodeGroupStorage: IStorage; /** * @type {string[]} */ private readonly rc4Keys: string[]; /** * @type {Map} */ private readonly stringLiteralCache: Map = new Map(); /** * @type {Map} */ private readonly stringLiteralHexadecimalIndexCache: Map = new Map(); /** * @type {IStorage} */ private readonly stringArrayStorage: IStorage; /** * @param customNodeGroupStorage * @param stringArrayStorage * @param options */ constructor ( @inject(ServiceIdentifiers.TCustomNodeGroupStorage) customNodeGroupStorage: IStorage, @inject(ServiceIdentifiers.TStringArrayStorage) stringArrayStorage: IStorage, @inject(ServiceIdentifiers.IOptions) options: IOptions ) { super(options); this.customNodeGroupStorage = customNodeGroupStorage; this.stringArrayStorage = stringArrayStorage; this.rc4Keys = RandomGeneratorUtils.getRandomGenerator() .n(() => RandomGeneratorUtils.getRandomGenerator().string({length: 4}), 50); } /** * @param nodeValue * @returns {string} */ public replace (nodeValue: string): string { const usingStringArray: boolean = ( this.options.stringArray && nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray && RandomGeneratorUtils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold ); const cacheKey: string = `${nodeValue}-${String(usingStringArray)}`; if (this.stringLiteralCache.has(cacheKey) && this.options.stringArrayEncoding !== StringArrayEncoding.rc4) { return this.stringLiteralCache.get(cacheKey); } let result: string; if (usingStringArray) { result = this.replaceStringLiteralWithStringArrayCall(nodeValue); } else { result = `'${Utils.stringToUnicodeEscapeSequence(nodeValue, !this.options.unicodeEscapeSequence)}'`; } this.stringLiteralCache.set(cacheKey, result); return result; } /** * @param value * @return {string} */ private getArrayHexadecimalIndex (value: string): string { if (this.stringLiteralHexadecimalIndexCache.has(value)) { return this.stringLiteralHexadecimalIndexCache.get(value); } const stringArrayStorageLength: number = this.stringArrayStorage.getLength(); const hexadecimalIndex: string = `${Utils.hexadecimalPrefix}${Utils.decToHex(stringArrayStorageLength)}`; this.stringArrayStorage.set(stringArrayStorageLength, value); this.stringLiteralHexadecimalIndexCache.set(value, hexadecimalIndex); return hexadecimalIndex; } /** * @param value * @returns {IEncodedValue} */ private getEncodedValue (value: string): IEncodedValue { let encodedValue: string, key: string | undefined; switch (this.options.stringArrayEncoding) { case StringArrayEncoding.rc4: key = RandomGeneratorUtils.getRandomGenerator().pickone(this.rc4Keys); encodedValue = CryptUtils.btoa(CryptUtils.rc4(value, key)); break; case StringArrayEncoding.base64: encodedValue = CryptUtils.btoa(value); break; default: encodedValue = value; } encodedValue = Utils.stringToUnicodeEscapeSequence(encodedValue, !this.options.unicodeEscapeSequence); return { encodedValue, key }; } /** * @param value * @returns {string} */ private replaceStringLiteralWithStringArrayCall (value: string): string { const { encodedValue, key }: IEncodedValue = this.getEncodedValue(value); const hexadecimalIndex: string = this.getArrayHexadecimalIndex(encodedValue); const rotatedStringArrayStorageId: string = Utils.stringRotate(this.stringArrayStorage.getStorageId(), 1); const stringArrayStorageCallsWrapperName: string = `_${Utils.hexadecimalPrefix}${rotatedStringArrayStorageId}`; if (key) { return `${stringArrayStorageCallsWrapperName}('${hexadecimalIndex}', '${Utils.stringToUnicodeEscapeSequence(key, !this.options.unicodeEscapeSequence)}')`; } return `${stringArrayStorageCallsWrapperName}('${hexadecimalIndex}')`; } }