mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
fix: handle nested parentheses in URL link detection (#7406)
The LinkifyIt library was truncating URLs containing nested parentheses, such as Kibana/RISON formatted links. For example, a URL like: https://example.com/?_g=(filters:!(),time:(from:now))&_a=(data) would be cut off at the first balanced parenthesis, losing the &_a=... portion. Added extendUrlWithBalancedParentheses helper function that: - Counts unbalanced opening parentheses in the detected URL - Extends the URL to include closing parens and following content - Stops at URL terminators (whitespace, quotes, angle brackets) - Stops if parentheses would become over-balanced (more closing than opening) Fixes #7402 Co-authored-by: Chirag Chandrashekhar <cchirag85@gmail.com>
This commit is contained in:
committed by
GitHub
parent
646c90819d
commit
79f9dbff9f
@@ -1,6 +1,32 @@
|
||||
import LinkifyIt from 'linkify-it';
|
||||
import { isMacOS } from 'utils/common/platform';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const URL_TERMINATORS = /[\s"<>\\`]/;
|
||||
const VALID_URL_CHARS = /^[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=%]/;
|
||||
|
||||
function extendUrlWithBalancedParentheses(url, line, endIndex) {
|
||||
let openParens = 0;
|
||||
for (const char of url) {
|
||||
if (char === '(') openParens++;
|
||||
else if (char === ')') openParens--;
|
||||
}
|
||||
if (openParens <= 0) return { url, lastIndex: endIndex };
|
||||
|
||||
let extendedUrl = url;
|
||||
let i = endIndex;
|
||||
while (i < line.length) {
|
||||
const char = line[i];
|
||||
if (URL_TERMINATORS.test(char) || !VALID_URL_CHARS.test(char)) break;
|
||||
if (char === '(') openParens++;
|
||||
else if (char === ')') openParens--;
|
||||
if (openParens < 0) break;
|
||||
extendedUrl += char;
|
||||
i++;
|
||||
}
|
||||
return { url: extendedUrl, lastIndex: i };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the visible line range using scroll info and lineAtHeight
|
||||
* @param {Object} editor - The CodeMirror editor instance
|
||||
@@ -71,14 +97,15 @@ function markUrls(editor, linkify, linkClass, linkHint) {
|
||||
);
|
||||
if (isInVariable) return;
|
||||
|
||||
const extended = extendUrlWithBalancedParentheses(url, lineContent, lastIndex);
|
||||
try {
|
||||
editor.markText(
|
||||
{ line: lineNum, ch: index },
|
||||
{ line: lineNum, ch: lastIndex },
|
||||
{ line: lineNum, ch: extended.lastIndex },
|
||||
{
|
||||
className: linkClass,
|
||||
attributes: {
|
||||
'data-url': url,
|
||||
'data-url': extended.url,
|
||||
'title': linkHint
|
||||
}
|
||||
}
|
||||
@@ -250,4 +277,4 @@ function setupLinkAware(editor, options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
export { setupLinkAware };
|
||||
export { setupLinkAware, extendUrlWithBalancedParentheses };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { setupLinkAware } from './linkAware';
|
||||
import { setupLinkAware, extendUrlWithBalancedParentheses } from './linkAware';
|
||||
import LinkifyIt from 'linkify-it';
|
||||
import { isMacOS } from 'utils/common/platform';
|
||||
|
||||
@@ -611,3 +611,34 @@ describe('setupLinkAware', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('extendUrlWithBalancedParentheses', () => {
|
||||
it('should not modify URLs with balanced parentheses', () => {
|
||||
const result = extendUrlWithBalancedParentheses('https://example.com/path?q=(a)', 'https://example.com/path?q=(a) end', 31);
|
||||
expect(result.url).toBe('https://example.com/path?q=(a)');
|
||||
});
|
||||
|
||||
it('should extend URL to balance nested parentheses', () => {
|
||||
const url = 'https://example.com?_g=(a:!(),b:(c:d';
|
||||
const line = 'https://example.com?_g=(a:!(),b:(c:d))&_a=(e) end';
|
||||
const result = extendUrlWithBalancedParentheses(url, line, 36);
|
||||
expect(result.url).toBe('https://example.com?_g=(a:!(),b:(c:d))&_a=(e)');
|
||||
});
|
||||
|
||||
it('should stop at whitespace', () => {
|
||||
const result = extendUrlWithBalancedParentheses('https://example.com?q=(a', 'https://example.com?q=(a ) end', 24);
|
||||
expect(result.url).toBe('https://example.com?q=(a');
|
||||
});
|
||||
|
||||
it('should stop when parentheses would become over-balanced', () => {
|
||||
const result = extendUrlWithBalancedParentheses('https://example.com', '(see https://example.com) end', 24);
|
||||
expect(result.url).toBe('https://example.com');
|
||||
});
|
||||
|
||||
it('should handle Kibana/RISON URLs with deeply nested parentheses', () => {
|
||||
const fullUrl = 'https://example.com/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-24h%2Fh,to:now))&_a=(columns:!(TopicName,key),dataSource:(dataViewId:f45f79b9,type:dataView),filters:!(),query:(language:kuery,query:\'%22test%22\'),sort:!(!(Timestamp,asc)))';
|
||||
const truncatedUrl = 'https://example.com/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-24h%2Fh,to:now)';
|
||||
const result = extendUrlWithBalancedParentheses(truncatedUrl, fullUrl, 120);
|
||||
expect(result.url).toBe(fullUrl);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user