From 821b2682188818322e73d8440d623d811db930f0 Mon Sep 17 00:00:00 2001 From: Bolaji Olajide <25608335+BolajiOlajide@users.noreply.github.com> Date: Wed, 10 Jan 2024 03:01:02 +0100 Subject: [PATCH 1/4] allow hex with prefix --- lib/types/string.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/string.js b/lib/types/string.js index 387c992b..12ad12ec 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -27,7 +27,7 @@ const internals = { } }, dataUriRegex: /^data:[\w+.-]+\/[\w+.-]+;((charset=[\w-]+|base64),)?(.*)$/, - hexRegex: /^[a-f0-9]+$/i, + hexRegex: /^(0x)?[0-9a-f]+$/i, ipRegex: Ip.regex({ cidr: 'forbidden' }).regex, isoDurationRegex: /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/, From b5322256a215f18f16cf5805ac4a6c00c9936589 Mon Sep 17 00:00:00 2001 From: Bolaji Olajide <25608335+BolajiOlajide@users.noreply.github.com> Date: Wed, 17 Jan 2024 00:54:45 +0100 Subject: [PATCH 2/4] add tests --- lib/types/string.js | 13 +++++++++---- test/types/string.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/types/string.js b/lib/types/string.js index 12ad12ec..dd4898fd 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -27,7 +27,10 @@ const internals = { } }, dataUriRegex: /^data:[\w+.-]+\/[\w+.-]+;((charset=[\w-]+|base64),)?(.*)$/, - hexRegex: /^(0x)?[0-9a-f]+$/i, + hexRegex: { + withPrefix: /^(0x)?[0-9a-f]+$/i, + withoutPrefix: /^[0-9a-f]+$/i + }, ipRegex: Ip.regex({ cidr: 'forbidden' }).regex, isoDurationRegex: /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/, @@ -368,16 +371,18 @@ module.exports = Any.extend({ hex: { method(options = {}) { - Common.assertOptions(options, ['byteAligned']); + Common.assertOptions(options, ['byteAligned', 'withPrefix']); - options = { byteAligned: false, ...options }; + options = { byteAligned: false, withPrefix: false, ...options }; Assert(typeof options.byteAligned === 'boolean', 'byteAligned must be boolean'); + Assert(typeof options.withPrefix === 'boolean', 'withPrefix must be boolean'); return this.$_addRule({ name: 'hex', args: { options } }); }, validate(value, helpers, { options }) { - if (!internals.hexRegex.test(value)) { + const re = options.withPrefix ? internals.hexRegex.withPrefix : internals.hexRegex.withoutPrefix; + if (!re.test(value)) { return helpers.error('string.hex'); } diff --git a/test/types/string.js b/test/types/string.js index 296ac415..687ccccc 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -4487,6 +4487,21 @@ describe('string', () => { }] ]); }); + + it('validates an hexadecimal string with prefix explicitly required', () => { + + const rule = Joi.string().hex({ withPrefix: true }).strict(); + Helper.validate(rule, [ + ['0x0123456789abcdef', true], + ['123456789abcdef', true], + ['0123afg', false, { + message: '"value" must only contain hexadecimal characters', + path: [], + type: 'string.hex', + context: { value: '0123afg', label: 'value' } + }] + ]); + }); }); describe('hostname()', () => { From 336a3382005f2edd3da1184f19d354ec6b6bcb02 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Thu, 18 Jan 2024 00:37:54 +0100 Subject: [PATCH 3/4] feat: improve over #3011 --- API.md | 3 +- lib/index.d.ts | 9 +++++ lib/types/string.js | 15 +++++--- test/types/string.js | 88 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 105 insertions(+), 10 deletions(-) diff --git a/API.md b/API.md index 06801211..f7d6653c 100755 --- a/API.md +++ b/API.md @@ -2823,8 +2823,9 @@ Requires the string value to be a valid hexadecimal string. - `options` - optional settings: - `byteAligned` - Boolean specifying whether you want to check that the hexadecimal string is byte aligned. If `convert` is `true`, a `0` will be added in front of the string in case it needs to be aligned. Defaults to `false`. + - `prefix` - Boolean or `optional`. When `true`, the string will be considered valid if prefixed with `0x` or `0X`. When `false`, the prefix is forbidden. When `optional`, the string will be considered valid if prefixed or not prefixed at all. Defaults to `false`. ```js -const schema = Joi.string().hex(); +const schema = Joi.string().hex({ prefix: 'optional' }); ``` Possible validation errors: [`string.hex`](#stringhex), [`string.hexAlign`](#stringhexalign) diff --git a/lib/index.d.ts b/lib/index.d.ts index 6b60ec37..d1d881e8 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -368,6 +368,15 @@ declare namespace Joi { * @default false */ byteAligned?: boolean; + /** + * controls whether the prefix `0x` or `0X` is allowed (or required) on hex strings. + * When `true`, the prefix must be provided. + * When `false`, the prefix is forbidden. + * When `optional`, the prefix is allowed but not required. + * + * @default false + */ + prefix?: boolean | 'optional'; } interface IpOptions { diff --git a/lib/types/string.js b/lib/types/string.js index dd4898fd..95ff546d 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -28,7 +28,8 @@ const internals = { }, dataUriRegex: /^data:[\w+.-]+\/[\w+.-]+;((charset=[\w-]+|base64),)?(.*)$/, hexRegex: { - withPrefix: /^(0x)?[0-9a-f]+$/i, + withPrefix: /^0x[0-9a-f]+$/i, + withOptionalPrefix: /^(?:0x)?[0-9a-f]+$/i, withoutPrefix: /^[0-9a-f]+$/i }, ipRegex: Ip.regex({ cidr: 'forbidden' }).regex, @@ -371,17 +372,21 @@ module.exports = Any.extend({ hex: { method(options = {}) { - Common.assertOptions(options, ['byteAligned', 'withPrefix']); + Common.assertOptions(options, ['byteAligned', 'prefix']); - options = { byteAligned: false, withPrefix: false, ...options }; + options = { byteAligned: false, prefix: false, ...options }; Assert(typeof options.byteAligned === 'boolean', 'byteAligned must be boolean'); - Assert(typeof options.withPrefix === 'boolean', 'withPrefix must be boolean'); + Assert(typeof options.prefix === 'boolean' || options.prefix === 'optional', 'prefix must be boolean or "optional"'); return this.$_addRule({ name: 'hex', args: { options } }); }, validate(value, helpers, { options }) { - const re = options.withPrefix ? internals.hexRegex.withPrefix : internals.hexRegex.withoutPrefix; + const re = options.prefix === 'optional' ? + internals.hexRegex.withOptionalPrefix : + options.prefix === true ? + internals.hexRegex.withPrefix : + internals.hexRegex.withoutPrefix; if (!re.test(value)) { return helpers.error('string.hex'); } diff --git a/test/types/string.js b/test/types/string.js index 687ccccc..cf293008 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -1215,6 +1215,58 @@ describe('string', () => { ] }); }); + + it('describes a hex string', () => { + + expect(Joi.string().hex().describe()).to.equal({ + type: 'string', + rules: [{ + name: 'hex', + args: { + options: { + byteAligned: false, + prefix: false + } + } + }] + }); + expect(Joi.string().hex({ byteAligned: true }).describe()).to.equal({ + type: 'string', + rules: [{ + name: 'hex', + args: { + options: { + byteAligned: true, + prefix: false + } + } + }] + }); + expect(Joi.string().hex({ prefix: true }).describe()).to.equal({ + type: 'string', + rules: [{ + name: 'hex', + args: { + options: { + byteAligned: false, + prefix: true + } + } + }] + }); + expect(Joi.string().hex({ prefix: 'optional' }).describe()).to.equal({ + type: 'string', + rules: [{ + name: 'hex', + args: { + options: { + byteAligned: false, + prefix: 'optional' + } + } + }] + }); + }); }); describe('domain()', () => { @@ -4490,15 +4542,43 @@ describe('string', () => { it('validates an hexadecimal string with prefix explicitly required', () => { - const rule = Joi.string().hex({ withPrefix: true }).strict(); + const rule = Joi.string().hex({ prefix: true }).strict(); Helper.validate(rule, [ + ['0123456789abcdef', false, { + message: '"value" must only contain hexadecimal characters', + path: [], + type: 'string.hex', + context: { value: '0123456789abcdef', label: 'value' } + }], ['0x0123456789abcdef', true], - ['123456789abcdef', true], - ['0123afg', false, { + ['0X0123456789abcdef', true] + ]); + }); + + it('validates an hexadecimal string with optional prefix', () => { + + const rule = Joi.string().hex({ prefix: 'optional' }).strict(); + Helper.validate(rule, [ + ['0123456789abcdef', true], + ['0x0123456789abcdef', true], + ['0X0123456789abcdef', true], + ['0123456789abcdefg', false, { message: '"value" must only contain hexadecimal characters', path: [], type: 'string.hex', - context: { value: '0123afg', label: 'value' } + context: { value: '0123456789abcdefg', label: 'value' } + }], + ['0x0123456789abcdefg', false, { + message: '"value" must only contain hexadecimal characters', + path: [], + type: 'string.hex', + context: { value: '0x0123456789abcdefg', label: 'value' } + }], + ['0X0123456789abcdefg', false, { + message: '"value" must only contain hexadecimal characters', + path: [], + type: 'string.hex', + context: { value: '0X0123456789abcdefg', label: 'value' } }] ]); }); From e7687b1ea526f7ed1e8c0b56e90b2c08f827d264 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Thu, 18 Jan 2024 00:43:04 +0100 Subject: [PATCH 4/4] 17.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e0d20596..d1edd1e1 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "joi", "description": "Object schema validation", - "version": "17.11.1", + "version": "17.12.0", "repository": "git://github.com/hapijs/joi", "main": "lib/index.js", "types": "lib/index.d.ts",