Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ describe('tokens_utils', () => {
const result = removeTrailingWhitespaces(url);
expect(result).toBe(url);
});
it(`doesn't strip if the first character is whitespace`, () => {
const url = ' _search trailing';
const result = removeTrailingWhitespaces(url);
expect(result).toBe(url);
});
it(`removes any text after the first whitespace`, () => {
const url = '_search some_text';
const result = removeTrailingWhitespaces(url);
Expand All @@ -31,6 +36,49 @@ describe('tokens_utils', () => {
const result = removeTrailingWhitespaces(url);
expect(result).toBe(url);
});
it(`doesn't treat a question mark inside quotes as query string start`, () => {
const url = '_search/"?literal" trailing_text';
const result = removeTrailingWhitespaces(url);
expect(result).toBe('_search/"?literal"');
});
it(`does not strip unquoted spaces inside query values`, () => {
const url = 'myindex/_search?q=type:organisation AND elastic';
const result = removeTrailingWhitespaces(url);
expect(result).toBe(url);
});
it.each([
[
'keeps slashes inside query values',
'myindex/_search?q=http://example.com/path AND elastic',
],
['keeps hashes inside query values', 'myindex/_search?q=tag#1 AND elastic'],
[
'keeps comment markers inside quoted query values',
'myindex/_search?q="organisation // elastic" AND kibana',
],
[
'uses the first question mark outside quotes as query string start',
'myindex/"?literal"/_search?q=type:organisation AND elastic',
],
])('%s', (_, url) => {
const result = removeTrailingWhitespaces(url);
expect(result).toBe(url);
});
it(`strips inline comment after unquoted query spaces`, () => {
const url = 'myindex/_search?q=type:organisation AND elastic // filter orgs';
const result = removeTrailingWhitespaces(url);
expect(result).toBe('myindex/_search?q=type:organisation AND elastic');
});
it(`strips inline comment after mixed whitespace in query values`, () => {
const url = 'myindex/_search?q=type:organisation AND elastic \t// filter orgs';
const result = removeTrailingWhitespaces(url);
expect(result).toBe('myindex/_search?q=type:organisation AND elastic');
});
it(`strips hash comment after unquoted query spaces`, () => {
const url = 'myindex/_search?q=type:organisation AND elastic # filter orgs';
const result = removeTrailingWhitespaces(url);
expect(result).toBe('myindex/_search?q=type:organisation AND elastic');
});
});

describe('parseLine', () => {
Expand All @@ -48,6 +96,24 @@ describe('tokens_utils', () => {
expect(urlPathTokens).toEqual(['_search']);
expect(urlParamsTokens[0]).toEqual(['query', '"test1 test2 test3"']);
});
it('preserves unquoted spaces inside query values', () => {
const { method, url, urlPathTokens, urlParamsTokens } = parseLine(
'GET myindex/_search?q=type:organisation AND elastic'
);
expect(method).toBe('GET');
expect(url).toBe('myindex/_search?q=type:organisation AND elastic');
expect(urlPathTokens).toEqual(['myindex', '_search']);
expect(urlParamsTokens[0]).toEqual(['q', 'type:organisation AND elastic']);
});
it('uses the first question mark outside quotes to parse url params', () => {
const { method, url, urlPathTokens, urlParamsTokens } = parseLine(
'GET myindex/"?literal"/_search?q=type:organisation AND elastic'
);
expect(method).toBe('GET');
expect(url).toBe('myindex/"?literal"/_search?q=type:organisation AND elastic');
expect(urlPathTokens).toEqual(['myindex', '"?literal"', '_search']);
expect(urlParamsTokens[0]).toEqual(['q', 'type:organisation AND elastic']);
});
it('works with multiple whitespaces', () => {
const { method, url, urlPathTokens, urlParamsTokens } = parseLine(
' GET _search?query="test1 test2 test3" // comment'
Expand Down Expand Up @@ -197,5 +263,12 @@ describe('tokens_utils', () => {
const result = parseUrl(url);
expect(result.urlPathTokens).toEqual(['_search', 'test']);
});

it('uses the first question mark outside quotes for url params', () => {
const url = 'myindex/"?literal"/_search?q=type:organisation AND elastic';
const result = parseUrl(url);
expect(result.urlPathTokens).toEqual(['myindex', '"?literal"', '_search']);
expect(result.urlParamsTokens[0]).toEqual(['q', 'type:organisation AND elastic']);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ export const parseUrl = (
} => {
let urlPathTokens: ParsedLineTokens['urlPathTokens'] = [];
let urlParamsTokens: ParsedLineTokens['urlParamsTokens'] = [];
const urlParts = url.split(questionMarkRegex);
const queryStringStart = getQueryStringStart(url);
// 1st part is the url path
const urlPath = urlParts[0];
const urlPath = queryStringStart >= 0 ? url.slice(0, queryStringStart) : url;
// try to parse into url path tokens (split on slashes, only keep non-empty tokens)
if (urlPath) {
urlPathTokens = urlPath.split(slashesRegex).filter(Boolean);
}
// 2nd part is the url params
const urlParams = urlParts[1];
const urlParams = queryStringStart >= 0 ? url.slice(queryStringStart + 1) : undefined;
// try to parse into url param tokens
if (urlParams) {
urlParamsTokens = urlParams.split(ampersandRegex).map((urlParamsPart) => {
Expand Down Expand Up @@ -409,28 +409,41 @@ export const parseBody = (value: string): string[] => {
};

/*
* This functions removes any trailing inline comments, for example
* This function removes trailing text after the request URL while preserving
* spaces in query values.
* For example:
* "_search // comment" -> "_search"
* Ideally the parser would do that, but currently they are included in url.
* "_search?q=foo AND bar // comment" -> "_search?q=foo AND bar"
* Ideally the parser would do that, but currently inline comments are included in url.
*/
export const removeTrailingWhitespaces = (url: string): string => {
let index = 0;
let whitespaceIndex = -1;
let isQueryParam = false;
let char = url[index];
while (char) {
if (char === '"') {
isQueryParam = !isQueryParam;
} else if (char === ' ' && !isQueryParam) {
whitespaceIndex = index;
break;
}
index++;
char = url[index];
if (url.startsWith(' ')) {
return url;
}
if (whitespaceIndex > 0) {
return url.slice(0, whitespaceIndex);

const queryStringStart = getQueryStringStart(url);
let isInQuotes = false;

for (let index = 0; index < url.length; index++) {
const char = url[index];

if (isDoubleQuote(char)) {
isInQuotes = !isInQuotes;
continue;
}

if (isInQuotes || char !== ' ') {
continue;
}

const isOutsideQueryString = queryStringStart < 0 || index < queryStringStart;
const shouldTrimTrailingText =
index > 0 && (isOutsideQueryString || isInlineCommentStart(url, index));
if (shouldTrimTrailingText) {
return url.slice(0, index);
}
}

return url;
};

Expand Down Expand Up @@ -476,6 +489,30 @@ const isHashChar = (char: string): boolean => {
const isSlash = (char: string): boolean => {
return char === '/';
};
const isWhitespaceChar = (char: string | undefined): boolean => {
return Boolean(char && whitespacesRegex.test(char));
};
const getQueryStringStart = (url: string): number => {
let isInQuotes = false;

for (let index = 0; index < url.length; index++) {
const char = url[index];
if (isDoubleQuote(char)) {
isInQuotes = !isInQuotes;
} else if (char === '?' && !isInQuotes) {
return index;
}
}

return -1;
};
const isInlineCommentStart = (url: string, index: number): boolean => {
let commentIndex = index;
while (isWhitespaceChar(url[commentIndex])) {
commentIndex++;
}
return url.startsWith('#', commentIndex) || url.startsWith('//', commentIndex);
};
const isStar = (char: string): boolean => {
return char === '*';
};
Expand Down
Loading