From 9af6f1f2347a4ffa4a131231b74978e1242fd585 Mon Sep 17 00:00:00 2001 From: Hyosik Philip Joo Date: Tue, 23 Apr 2024 15:53:44 +0900 Subject: [PATCH] feat: Support encoding uri (#3027) * feat: Add encodeUri in UriOptions interface * feat: Add encodeUri in options list for uri method * feat: Encode uri with true flags in uri validation * feat: Add test for uri encoding * fix: Integrate lint suggestions for style consistency * fix: Pre-encode value before other cases * feat: Add custom error message for uriEncoding * feat: Add case for uriEncoding convert false * docs: Add description for encodeUri --- API.md | 1 + lib/index.d.ts | 6 ++++++ lib/types/string.js | 11 ++++++++++- test/types/string.js | 23 +++++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index f7d6653c..b74d8782 100755 --- a/API.md +++ b/API.md @@ -3085,6 +3085,7 @@ Requires the string value to be a valid [RFC 3986](http://tools.ietf.org/html/rf - `relativeOnly` - Restrict only relative URIs. Defaults to `false`. - `allowQuerySquareBrackets` - Allows unencoded square brackets inside the query string. This is **NOT** RFC 3986 compliant but query strings like `abc[]=123&abc[]=456` are very common these days. Defaults to `false`. - `domain` - Validate the domain component using the options specified in [`string.domain()`](#stringdomainoptions). + - `encodeUri` - Encodes the uri with non-alphabetical characters. Defaults to `false`. ```js // Accept git or git http/https diff --git a/lib/index.d.ts b/lib/index.d.ts index 3a50bfbc..2aac4b30 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -426,6 +426,12 @@ declare namespace Joi { * Validate the domain component using the options specified in `string.domain()`. */ domain?: DomainOptions; + /** + * Encode URI before validation. + * + * @default false + */ + encodeUri?: boolean; } interface DataUriOptions { diff --git a/lib/types/string.js b/lib/types/string.js index c86aa391..5ddbe6d5 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -649,7 +649,7 @@ module.exports = Any.extend({ uri: { method(options = {}) { - Common.assertOptions(options, ['allowRelative', 'allowQuerySquareBrackets', 'domain', 'relativeOnly', 'scheme']); + Common.assertOptions(options, ['allowRelative', 'allowQuerySquareBrackets', 'domain', 'relativeOnly', 'scheme', 'encodeUri']); if (options.domain) { Common.assertOptions(options.domain, ['allowFullyQualified', 'allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']); @@ -665,6 +665,10 @@ module.exports = Any.extend({ return helpers.error('string.uri'); } + if (helpers.prefs.convert && options.encodeUri) { + value = encodeURI(value); + } + const match = regex.exec(value); if (match) { const matched = match[1] || match[2]; @@ -686,6 +690,10 @@ module.exports = Any.extend({ return helpers.error('string.uriCustomScheme', { scheme, value }); } + if (options.encodeUri) { + return helpers.error('string.uriEncoding'); + } + return helpers.error('string.uri'); } } @@ -736,6 +744,7 @@ module.exports = Any.extend({ 'string.uri': '{{#label}} must be a valid uri', 'string.uriCustomScheme': '{{#label}} must be a valid uri with a scheme matching the {{#scheme}} pattern', 'string.uriRelativeOnly': '{{#label}} must be a valid relative uri', + 'string.uriEncoding': '{{#label}} must contain only valid characters or "convert" must be allowed', 'string.uppercase': '{{#label}} must only contain uppercase characters' } }); diff --git a/test/types/string.js b/test/types/string.js index 64c65ee6..ac96dae3 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -9012,6 +9012,29 @@ describe('string', () => { ]); }); + it('validates uri with accented characters with encoding', () => { + + const schema = Joi.string().uri({ encodeUri: true }); + + Helper.validate(schema, { convert: true }, [ + ['https://linkedin.com/in/aïssa/', true, 'https://linkedin.com/in/a%C3%AFssa/'] + ]); + }); + + it('validates uri with accented characters without encoding', () => { + + const schema = Joi.string().uri({ encodeUri: true }); + + Helper.validate(schema, { convert: false }, [ + ['https://linkedin.com/in/aïssa/', false, { + message: '"value" must contain only valid characters or "convert" must be allowed', + path: [], + type: 'string.uriEncoding', + context: { value: 'https://linkedin.com/in/aïssa/', label: 'value' } + }] + ]); + }); + it('errors on unknown options', () => { expect(() => Joi.string().uri({ foo: 'bar', baz: 'qux' })).to.throw('Options contain unknown keys: foo,baz');