From 1cd0b47e6b355ca601330dc35ac597b22a4b726a Mon Sep 17 00:00:00 2001 From: Ghislain Beaulac Date: Tue, 29 Oct 2019 18:07:24 -0400 Subject: [PATCH] fix(odata): filter with single quote should be escaped, fixes #328 --- .../__tests__/grid-odata.service.spec.ts | 18 +++++++++++++++++- .../services/grid-odata.service.ts | 10 ++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/app/modules/angular-slickgrid/services/__tests__/grid-odata.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/grid-odata.service.spec.ts index e7c0d4b0a..1ba8a1186 100644 --- a/src/app/modules/angular-slickgrid/services/__tests__/grid-odata.service.spec.ts +++ b/src/app/modules/angular-slickgrid/services/__tests__/grid-odata.service.spec.ts @@ -576,6 +576,22 @@ describe('GridOdataService', () => { expect(query).toBe(expectation); }); + it('should escape single quote by doubling the quote when filter includes a single quote', () => { + const expectation = `$top=10&$filter=(Gender eq 'female' and not substringof('abc''s', Company))`; + const mockColumnGender = { id: 'gender', field: 'gender' } as Column; + const mockColumnCompany = { id: 'company', field: 'company' } as Column; + const mockColumnFilters = { + gender: { columnId: 'gender', columnDef: mockColumnGender, searchTerms: ['female'], operator: 'EQ' }, + company: { columnId: 'company', columnDef: mockColumnCompany, searchTerms: [`abc's`], operator: OperatorType.notContains }, + } as ColumnFilters; + + service.init(serviceOptions, paginationOptions, gridStub); + service.updateFilters(mockColumnFilters, false); + const query = service.buildQuery(); + + expect(query).toBe(expectation); + }); + it('should return a query with multiple filters and expect same query string result as previous test even with "isUpdatedByPreset" enabled', () => { const expectation = `$top=10&$filter=(Gender eq 'female' and substringof('abc', Company))`; const mockColumnGender = { id: 'gender', field: 'gender' } as Column; @@ -1349,7 +1365,7 @@ describe('GridOdataService', () => { expect(currentFilters).toEqual(presetFilters); }); - it('should return a query with a filter with range of numbers with decimals when the preset is a filter range with 3 dots (..) separator', () => { + it('should return a query with a filter with range of numbers with decimals when the preset is a filter range with 3 dots (...) separator', () => { serviceOptions.columnDefinitions = [{ id: 'company', field: 'company' }, { id: 'gender', field: 'gender' }, { id: 'duration', field: 'duration', type: FieldType.number }]; const expectation = `$top=10&$filter=(Duration gt 0.5 and Duration lt .88)`; const presetFilters = [ diff --git a/src/app/modules/angular-slickgrid/services/grid-odata.service.ts b/src/app/modules/angular-slickgrid/services/grid-odata.service.ts index e0c9991dc..042f371f4 100644 --- a/src/app/modules/angular-slickgrid/services/grid-odata.service.ts +++ b/src/app/modules/angular-slickgrid/services/grid-odata.service.ts @@ -327,7 +327,8 @@ export class GridOdataService implements BackendService { if (operator === 'IN') { // example:: (Stage eq "Expired" or Stage eq "Renewal") for (let j = 0, lnj = searchTerms.length; j < lnj; j++) { - tmpSearchTerms.push(`${fieldName} eq '${searchTerms[j]}'`); + const searchVal = searchTerms[j].replace(`'`, `''`); + tmpSearchTerms.push(`${fieldName} eq '${searchVal}'`); } searchBy = tmpSearchTerms.join(' or '); if (!(typeof searchBy === 'string' && searchBy[0] === '(' && searchBy.slice(-1) === ')')) { @@ -336,7 +337,8 @@ export class GridOdataService implements BackendService { } else { // example:: (Stage ne "Expired" and Stage ne "Renewal") for (let k = 0, lnk = searchTerms.length; k < lnk; k++) { - tmpSearchTerms.push(`${fieldName} ne '${searchTerms[k]}'`); + const searchVal = searchTerms[k].replace(`'`, `''`); + tmpSearchTerms.push(`${fieldName} ne '${searchVal}'`); } searchBy = tmpSearchTerms.join(' and '); if (!(typeof searchBy === 'string' && searchBy[0] === '(' && searchBy.slice(-1) === ')')) { @@ -349,7 +351,7 @@ export class GridOdataService implements BackendService { } else if (fieldType === FieldType.string) { // string field needs to be in single quotes if (operator === '' || operator === OperatorType.contains || operator === OperatorType.notContains) { - searchBy = this.odataQueryVersionWrapper('substring', odataVersion, fieldName, searchTerms); + searchBy = this.odataQueryVersionWrapper('substring', odataVersion, fieldName, searchValue); if (operator === OperatorType.notContains) { searchBy = `not ${searchBy}`; } @@ -373,7 +375,7 @@ export class GridOdataService implements BackendService { // push to our temp array and also trim white spaces if (searchBy !== '') { searchByArray.push(searchBy.trim()); - this.saveColumnFilter(fieldName || '', fieldSearchValue, searchTerms); + this.saveColumnFilter(fieldName || '', fieldSearchValue, searchValue); } } }