diff --git a/CHANGELOG.md b/CHANGELOG.md index e4baedb..dffc95a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### Version: 5.0.1 +#### Date: feb-23-2026 +Fix: Added support of special symbols in regex method with safe pattern. + ### Version: 5.0.0 #### Date: Feb-16-2026 Breaking: Cache persistence is now a separate plugin. When using a cache policy other than `IGNORE_CACHE`, you must pass `cacheOptions.persistenceStore`. Install `@contentstack/persistence-plugin` and use `new PersistenceStore({ ... })` as the store. The SDK no longer bundles persistence code or accepts `storeType` in `cacheOptions`. diff --git a/package-lock.json b/package-lock.json index da1151c..ee39539 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/delivery-sdk", - "version": "5.0.0", + "version": "5.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/delivery-sdk", - "version": "5.0.0", + "version": "5.0.1", "license": "MIT", "dependencies": { "@contentstack/core": "^1.3.10", @@ -2689,9 +2689,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index cfdf384..abc8bb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/delivery-sdk", - "version": "5.0.0", + "version": "5.0.1", "type": "module", "license": "MIT", "engines": { diff --git a/src/query/query.ts b/src/query/query.ts index 0c49008..e3f5180 100644 --- a/src/query/query.ts +++ b/src/query/query.ts @@ -29,17 +29,20 @@ export class Query extends BaseQuery { return alphanumericRegex.test(input); } - // Validate if input matches any of the safe, pre-approved patterns + // Validate if input matches safe regex patterns private isValidRegexPattern(input: string): boolean { - const validRegex = /^[a-zA-Z0-9|^$.*+?()[\]{}\\-]+$/; // Allow only safe regex characters + // Expanded whitelist: includes spaces and common safe special characters + // Allows: alphanumeric, regex metacharacters, regular spaces, and common punctuation + // Blocks: control characters (newlines, tabs, null bytes), backticks, and other dangerous chars + const validRegex = /^[a-zA-Z0-9|^$.*+?()[\]{}:,;&@#%=/!'"_~<> -]+$/; if (!validRegex.test(input)) { - return false; + return false; } try { - new RegExp(input); - return true; + new RegExp(input); + return true; } catch (e) { - return false; + return false; } } diff --git a/test/unit/content-validation-comprehensive.spec.ts b/test/unit/content-validation-comprehensive.spec.ts index b50dbfc..f7d45ab 100644 --- a/test/unit/content-validation-comprehensive.spec.ts +++ b/test/unit/content-validation-comprehensive.spec.ts @@ -698,6 +698,25 @@ describe('Content Validation - Comprehensive Test Suite', () => { expect(() => query.regex('title', '*invalid')).toThrow(ErrorMessages.INVALID_REGEX_PATTERN); }); + it('should accept regex patterns with spaces and special characters in blog queries', () => { + const query = new Query(client, {}, {}, '', 'blog_post'); + + // Patterns with spaces + expect(() => query.regex('title', '.*blog post.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*global flex.*', 'i')).not.toThrow(); + + // Patterns with punctuation + expect(() => query.regex('title', '.*test:value.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*test,value.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*test&value.*', 'i')).not.toThrow(); + expect(() => query.regex('content', '.*https://example.com.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*test#tag.*', 'i')).not.toThrow(); + + // Verify parameters are set correctly + query.regex('title', '.*search term.*', 'i'); + expect(query._parameters.title).toEqual({ $regex: '.*search term.*', $options: 'i' }); + }); + it('should validate query value types', () => { const query = new Query(client, {}, {}, '', 'blog_post'); diff --git a/test/unit/query-optimization-comprehensive.spec.ts b/test/unit/query-optimization-comprehensive.spec.ts index d1257e1..7042fb6 100644 --- a/test/unit/query-optimization-comprehensive.spec.ts +++ b/test/unit/query-optimization-comprehensive.spec.ts @@ -195,6 +195,23 @@ describe('Query Optimization - Comprehensive Test Suite', () => { expect(() => query.regex('title', '*invalid')).toThrow(ErrorMessages.INVALID_REGEX_PATTERN); }); + it('should accept regex patterns with spaces and special characters', () => { + // Patterns with spaces (user search scenarios) + expect(() => query.regex('title', '.*test er.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*global flex.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*two words.*', 'i')).not.toThrow(); + + // Patterns with special characters + expect(() => query.regex('title', '.*test:value.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*test,value.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*test&value.*', 'i')).not.toThrow(); + expect(() => query.regex('email', '.*@example.com.*', 'i')).not.toThrow(); + expect(() => query.regex('url', '.*https://site.com.*', 'i')).not.toThrow(); + expect(() => query.regex('title', '.*test#tag.*', 'i')).not.toThrow(); + expect(() => query.regex('title', ".*test'value.*", 'i')).not.toThrow(); + expect(() => query.regex('title', '.*test_value.*', 'i')).not.toThrow(); + }); + it('should validate containedIn values for proper types', () => { const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); diff --git a/test/unit/query.spec.ts b/test/unit/query.spec.ts index 766abf0..e6061c3 100644 --- a/test/unit/query.spec.ts +++ b/test/unit/query.spec.ts @@ -89,9 +89,10 @@ describe('Query class', () => { expect(() => regexQuery.regex("fieldUid", "[a-z")).toThrow("Invalid regexPattern: Must be a valid regular expression"); }); - it('should throw error when regex method is called with invalid characters', async () => { + it('should throw error when regex method is called with invalid regex pattern', async () => { const regexQuery = getQueryObject(client, 'referenced-content-type-uid'); - expect(() => regexQuery.regex("fieldUid", "test