diff --git a/packages/bruno-common/src/utils/template-hasher.spec.ts b/packages/bruno-common/src/utils/template-hasher.spec.ts new file mode 100644 index 0000000000..e4ad1aeeea --- /dev/null +++ b/packages/bruno-common/src/utils/template-hasher.spec.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from '@jest/globals'; +import { patternHasher } from './template-hasher'; + +describe('patternHasher', () => { + it('hashes and restore are mathematically reproducible', () => { + const originalUrl = '{{host}}.example.com'; + const { hashed, restore } = patternHasher(originalUrl); + expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash--163450413.example.com"`); + expect(restore(hashed)).toEqual(originalUrl); + }); + + it('hashes more than once', () => { + const originalUrl = '{{host}}.example.{{new}}'; + const { hashed, restore } = patternHasher(originalUrl); + expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash--163450413.example.bruno-var-hash-652560383"`); + expect(restore(hashed)).toEqual(originalUrl); + }); + + it('allows custom matchers', () => { + const originalUrl = '$name.example.com'; + const { hashed, restore } = patternHasher(originalUrl, /\$(\w+)/); + expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash-180907786.example.com"`); + expect(restore(hashed)).toEqual(originalUrl); + }); + + it('ignore unless matched', () => { + const originalUrl = '$name.example.com'; + const { hashed, restore } = patternHasher(originalUrl); + expect(hashed).toMatchInlineSnapshot(`"$name.example.com"`); + expect(restore(hashed)).toEqual(originalUrl); + }); +}); diff --git a/packages/bruno-common/src/utils/template-hasher.ts b/packages/bruno-common/src/utils/template-hasher.ts new file mode 100644 index 0000000000..290c8a3e9a --- /dev/null +++ b/packages/bruno-common/src/utils/template-hasher.ts @@ -0,0 +1,49 @@ +const VARIABLE_REGEX = /\{\{([^}]+)\}\}/g; + +/** + * Was implemented specifically for request.url where the url might have variables + * that might need to be sanitised before being passed to a URL validator that doesn't + * allow special characters that bruno uses as variables (`{{var_name}}`) + * + * The function replaces the input string with a unique hash that can be restored + * later by the helper returned by this function + */ +export function patternHasher(input: string, pattern: string | RegExp = VARIABLE_REGEX) { + const usableRegex = new RegExp(pattern, 'g'); + function hash(toHash: string) { + let hash = 5381; + let c; + for (let i = 0; i < toHash.length; i++) { + c = toHash.charCodeAt(i); + hash = ((hash << 5) + hash + c) | 0; + } + return '' + hash; + } + + const prefix = `bruno-var-hash-`; + const hashToOriginal: Record = {}; + let result = input; + let hashed = false; + if (usableRegex.test(input)) { + hashed = true; + result = input.replace(usableRegex, function (matchedVar) { + const hashedValue = `${prefix}${hash(matchedVar)}`; + hashToOriginal[hashedValue] = matchedVar; + return hashedValue; + }); + } + return { + hashed: result, + restore(current: string) { + if (!hashed) { + return current; + } + let clone = current; + for (const hash in hashToOriginal) { + const value = hashToOriginal[hash]; + clone = clone.replace(hash, value); + } + return clone; + } + }; +}