Skip to content

Commit 68b2625

Browse files
barelyhumansid-brunoCopilot
authored
feat(common): add patternHasher utility for hashing and restoring string from special characters (#6032)
* feat: add patternHasher utility for variable hashing This utility function hashes input strings containing variables and allows for restoration of the original string. It includes support for custom matchers and handles cases where no variables are matched. * Update packages/bruno-common/src/utils/template-hasher.ts Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Sid <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 1719cee commit 68b2625

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { describe, it, expect } from '@jest/globals';
2+
import { patternHasher } from './template-hasher';
3+
4+
describe('patternHasher', () => {
5+
it('hashes and restore are mathematically reproducible', () => {
6+
const originalUrl = '{{host}}.example.com';
7+
const { hashed, restore } = patternHasher(originalUrl);
8+
expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash--163450413.example.com"`);
9+
expect(restore(hashed)).toEqual(originalUrl);
10+
});
11+
12+
it('hashes more than once', () => {
13+
const originalUrl = '{{host}}.example.{{new}}';
14+
const { hashed, restore } = patternHasher(originalUrl);
15+
expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash--163450413.example.bruno-var-hash-652560383"`);
16+
expect(restore(hashed)).toEqual(originalUrl);
17+
});
18+
19+
it('allows custom matchers', () => {
20+
const originalUrl = '$name.example.com';
21+
const { hashed, restore } = patternHasher(originalUrl, /\$(\w+)/);
22+
expect(hashed).toMatchInlineSnapshot(`"bruno-var-hash-180907786.example.com"`);
23+
expect(restore(hashed)).toEqual(originalUrl);
24+
});
25+
26+
it('ignore unless matched', () => {
27+
const originalUrl = '$name.example.com';
28+
const { hashed, restore } = patternHasher(originalUrl);
29+
expect(hashed).toMatchInlineSnapshot(`"$name.example.com"`);
30+
expect(restore(hashed)).toEqual(originalUrl);
31+
});
32+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const VARIABLE_REGEX = /\{\{([^}]+)\}\}/g;
2+
3+
/**
4+
* Was implemented specifically for request.url where the url might have variables
5+
* that might need to be sanitised before being passed to a URL validator that doesn't
6+
* allow special characters that bruno uses as variables (`{{var_name}}`)
7+
*
8+
* The function replaces the input string with a unique hash that can be restored
9+
* later by the helper returned by this function
10+
*/
11+
export function patternHasher(input: string, pattern: string | RegExp = VARIABLE_REGEX) {
12+
const usableRegex = new RegExp(pattern, 'g');
13+
function hash(toHash: string) {
14+
let hash = 5381;
15+
let c;
16+
for (let i = 0; i < toHash.length; i++) {
17+
c = toHash.charCodeAt(i);
18+
hash = ((hash << 5) + hash + c) | 0;
19+
}
20+
return '' + hash;
21+
}
22+
23+
const prefix = `bruno-var-hash-`;
24+
const hashToOriginal: Record<string, string> = {};
25+
let result = input;
26+
let hashed = false;
27+
if (usableRegex.test(input)) {
28+
hashed = true;
29+
result = input.replace(usableRegex, function (matchedVar) {
30+
const hashedValue = `${prefix}${hash(matchedVar)}`;
31+
hashToOriginal[hashedValue] = matchedVar;
32+
return hashedValue;
33+
});
34+
}
35+
return {
36+
hashed: result,
37+
restore(current: string) {
38+
if (!hashed) {
39+
return current;
40+
}
41+
let clone = current;
42+
for (const hash in hashToOriginal) {
43+
const value = hashToOriginal[hash];
44+
clone = clone.replace(hash, value);
45+
}
46+
return clone;
47+
}
48+
};
49+
}

0 commit comments

Comments
 (0)