From 4234132ba16c1f25b6abd4347beaef170f5272fc Mon Sep 17 00:00:00 2001 From: trizotti Date: Fri, 23 Sep 2022 10:48:28 -0300 Subject: [PATCH 01/14] Add hostname option to domain obj --- 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 cdaffc40..7cbcb638 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -639,7 +639,7 @@ module.exports = Any.extend({ Common.assertOptions(options, ['allowRelative', 'allowQuerySquareBrackets', 'domain', 'relativeOnly', 'scheme']); if (options.domain) { - Common.assertOptions(options.domain, ['allowFullyQualified', 'allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']); + Common.assertOptions(options.domain, ['allowFullyQualified', 'allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds', 'hostname']); } const { regex, scheme } = Uri.regex(options); From b23ce5172ca02e50bbc6da78b9e9764ed283a7ee Mon Sep 17 00:00:00 2001 From: trizotti Date: Fri, 23 Sep 2022 14:51:44 -0300 Subject: [PATCH 02/14] Add hostname validation + tests --- lib/types/string.js | 9 +++++++++ test/types/string.js | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/types/string.js b/lib/types/string.js index 7cbcb638..3dd51bde 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -10,6 +10,7 @@ const Uri = require('@sideway/address/lib/uri'); const Any = require('./any'); const Common = require('../common'); +const { throws } = require('assert') const internals = { @@ -662,6 +663,13 @@ module.exports = Any.extend({ return helpers.error('string.domain', { value: matched }); } + if(options?.domain?.hostname){ + const { hostname } = options.domain; + if(hostname !== matched){ + return helpers.error('string.uri.hostname', { value: matched }); + } + } + return value; } @@ -721,6 +729,7 @@ module.exports = Any.extend({ 'string.pattern.invert.name': '{{#label}} with value {:[.]} matches the inverted {{#name}} pattern', 'string.trim': '{{#label}} must not have leading or trailing whitespace', 'string.uri': '{{#label}} must be a valid uri', + 'string.uri.hostname': '{{#label}} is not from given hostname', '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.uppercase': '{{#label}} must only contain uppercase characters' diff --git a/test/types/string.js b/test/types/string.js index 874b4659..a7e4be74 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -8582,6 +8582,16 @@ describe('string', () => { expect(() => Joi.string().uri({ foo: 'bar', baz: 'qux' })).to.throw('Options contain unknown keys: foo,baz'); }); + + it('validates if uri is from a given hostname', () => { + const schema = Joi.string().uri({ scheme: 'https', domain: { hostname: 'example.com'}}); + Helper.validate(schema, [ + ['https://example.com', true], + ['https://example.com/test', true], + ['https://test.com', false, '"value" is not from given hostname'], + ['https://test.com/example', false, '"value" is not from given hostname'] + ]); + }); }); describe('valid()', () => { From f1f0322e4cd040fb4dd66c2502f65cf5a81e306e Mon Sep 17 00:00:00 2001 From: trizotti Date: Fri, 23 Sep 2022 14:59:33 -0300 Subject: [PATCH 03/14] Remove unnecessary import --- lib/types/string.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/types/string.js b/lib/types/string.js index 3dd51bde..b338523d 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -10,7 +10,6 @@ const Uri = require('@sideway/address/lib/uri'); const Any = require('./any'); const Common = require('../common'); -const { throws } = require('assert') const internals = { From 25777d5d70fe1023bce717ce9b588c9c678f8dc2 Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 3 Oct 2022 10:39:42 -0300 Subject: [PATCH 04/14] Fix lint issues --- lib/types/string.js | 4 ++-- test/types/string.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/types/string.js b/lib/types/string.js index b338523d..8fb69e0f 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -662,9 +662,9 @@ module.exports = Any.extend({ return helpers.error('string.domain', { value: matched }); } - if(options?.domain?.hostname){ + if (options?.domain?.hostname) { const { hostname } = options.domain; - if(hostname !== matched){ + if (hostname !== matched) { return helpers.error('string.uri.hostname', { value: matched }); } } diff --git a/test/types/string.js b/test/types/string.js index a7e4be74..fe1c6191 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -8584,10 +8584,11 @@ describe('string', () => { }); it('validates if uri is from a given hostname', () => { - const schema = Joi.string().uri({ scheme: 'https', domain: { hostname: 'example.com'}}); + + const schema = Joi.string().uri({ scheme: 'https', domain: { hostname: 'example.com' } }); Helper.validate(schema, [ ['https://example.com', true], - ['https://example.com/test', true], + ['https://example.com/test', true], ['https://test.com', false, '"value" is not from given hostname'], ['https://test.com/example', false, '"value" is not from given hostname'] ]); From 3cb8376f63144b606aaed5af84751e855a0a6a2a Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 3 Oct 2022 14:29:45 -0300 Subject: [PATCH 05/14] Add support for array of hostnames + tests --- lib/types/string.js | 11 ++++++++--- test/types/string.js | 13 ++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/types/string.js b/lib/types/string.js index 8fb69e0f..8a8ea909 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -662,11 +662,16 @@ module.exports = Any.extend({ return helpers.error('string.domain', { value: matched }); } - if (options?.domain?.hostname) { - const { hostname } = options.domain; - if (hostname !== matched) { + if (domain.hostname) { + if (typeof domain.hostname === 'string' && domain.hostname !== matched) { return helpers.error('string.uri.hostname', { value: matched }); } + + if (Array.isArray(domain.hostname)) { + if (!domain.hostname.find((item) => item === matched)) { + return helpers.error('string.uri.hostname', { value: matched }); + } + } } return value; diff --git a/test/types/string.js b/test/types/string.js index fe1c6191..17936eea 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -8583,15 +8583,22 @@ describe('string', () => { expect(() => Joi.string().uri({ foo: 'bar', baz: 'qux' })).to.throw('Options contain unknown keys: foo,baz'); }); - it('validates if uri is from a given hostname', () => { + it.only('validates if uri is from one or more hostnames', () => { - const schema = Joi.string().uri({ scheme: 'https', domain: { hostname: 'example.com' } }); - Helper.validate(schema, [ + const stringSchema = Joi.string().uri({ scheme: 'https', domain: { hostname: 'example.com' } }); + Helper.validate(stringSchema, [ ['https://example.com', true], ['https://example.com/test', true], ['https://test.com', false, '"value" is not from given hostname'], ['https://test.com/example', false, '"value" is not from given hostname'] ]); + + const arraySchema = Joi.string().uri({ scheme: 'https', domain: { hostname: ['example.com', 'dummy.org'] } }); + Helper.validate(arraySchema, [ + ['https://example.com/test', true], + ['https://test.com/example', false, '"value" is not from given hostname'], + ['https://dummy.org/test', true] + ]); }); }); From a90120bea7daf64bc00315a8d2a3bb272c2e63a0 Mon Sep 17 00:00:00 2001 From: trizotti Date: Thu, 13 Oct 2022 09:31:28 -0300 Subject: [PATCH 06/14] Add support to mixed arrays rx and strings --- lib/types/string.js | 55 ++++++++++++++++++++++++++++++++++---------- test/types/string.js | 19 ++++++++++----- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/lib/types/string.js b/lib/types/string.js index 8a8ea909..b1b7ab02 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -655,21 +655,52 @@ module.exports = Any.extend({ const match = regex.exec(value); if (match) { const matched = match[1] || match[2]; - if (domain && - (!options.allowRelative || matched) && - !Domain.isValid(matched, domain)) { - return helpers.error('string.domain', { value: matched }); - } + if (domain) { + + if ((!options.allowRelative || matched) && + !Domain.isValid(matched, domain)) { - if (domain.hostname) { - if (typeof domain.hostname === 'string' && domain.hostname !== matched) { - return helpers.error('string.uri.hostname', { value: matched }); + return helpers.error('string.domain', { value: matched }); } - if (Array.isArray(domain.hostname)) { - if (!domain.hostname.find((item) => item === matched)) { - return helpers.error('string.uri.hostname', { value: matched }); + if (domain.hostname) { + + const isMatch = (matcher) => { + + if (matcher instanceof RegExp) { + return !!matcher.test(matched); + } + + if (typeof matcher === 'string') { + return matcher === matched; + } + + return false; + }; + + if (typeof domain.hostname === 'string' || domain.hostname instanceof RegExp) { + + if (!isMatch(domain.hostname)) { + return helpers.error('string.uri.hostname', { value: matched, expected: String(domain.hostname) }); + } + + } + + if (Array.isArray(domain.hostname)) { + + let matches = 0; + domain.hostname.forEach((matcher) => { + + if (isMatch(matcher)) { + matches++; + } + }); + + if (matches === 0) { + return helpers.error('string.uri.hostname', { value: matched, expected: 'any of the hostname items' }); + } + } } } @@ -733,7 +764,7 @@ module.exports = Any.extend({ 'string.pattern.invert.name': '{{#label}} with value {:[.]} matches the inverted {{#name}} pattern', 'string.trim': '{{#label}} must not have leading or trailing whitespace', 'string.uri': '{{#label}} must be a valid uri', - 'string.uri.hostname': '{{#label}} is not from given hostname', + 'string.uri.hostname': '{{#label}} does not match {{#expected}}', '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.uppercase': '{{#label}} must only contain uppercase characters' diff --git a/test/types/string.js b/test/types/string.js index 17936eea..6c1ce6e0 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -8583,21 +8583,28 @@ describe('string', () => { expect(() => Joi.string().uri({ foo: 'bar', baz: 'qux' })).to.throw('Options contain unknown keys: foo,baz'); }); - it.only('validates if uri is from one or more hostnames', () => { + it('validates domain.hostname', () => { const stringSchema = Joi.string().uri({ scheme: 'https', domain: { hostname: 'example.com' } }); Helper.validate(stringSchema, [ ['https://example.com', true], ['https://example.com/test', true], - ['https://test.com', false, '"value" is not from given hostname'], - ['https://test.com/example', false, '"value" is not from given hostname'] + ['https://test.com', false, '"value" does not match example.com'], + ['https://test.com/example', false, '"value" does not match example.com'] ]); - const arraySchema = Joi.string().uri({ scheme: 'https', domain: { hostname: ['example.com', 'dummy.org'] } }); + const regexSchema = Joi.string().uri({ scheme: 'https', domain: { hostname: /example.com/ } }); + Helper.validate(regexSchema, [ + ['https://example.com', true], + ['https://dummy.com', false, '"value" does not match /example.com/'] + ]); + + const arraySchema = Joi.string().uri({ scheme: 'https', domain: { hostname: ['example.com', 'dummy.org', /dummy.com/] } }); Helper.validate(arraySchema, [ ['https://example.com/test', true], - ['https://test.com/example', false, '"value" is not from given hostname'], - ['https://dummy.org/test', true] + ['https://test.com/example', false, '"value" does not match any of the hostname items'], + ['https://dummy.org/test', true], + ['https://dummy.com/test', true] ]); }); }); From 4d29babf8239c11bb36ba83704aef56ce83270d7 Mon Sep 17 00:00:00 2001 From: trizotti Date: Thu, 13 Oct 2022 13:17:25 -0300 Subject: [PATCH 07/14] Remove unecessary operator --- 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 b1b7ab02..8f0c4741 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -669,7 +669,7 @@ module.exports = Any.extend({ const isMatch = (matcher) => { if (matcher instanceof RegExp) { - return !!matcher.test(matched); + return matcher.test(matched); } if (typeof matcher === 'string') { From 2e754a3be9dcc36175249578abe9b2152b366e5c Mon Sep 17 00:00:00 2001 From: trizotti Date: Thu, 13 Oct 2022 16:45:22 -0300 Subject: [PATCH 08/14] Add docs for string.uri.hostname --- API.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/API.md b/API.md index 3f2f2427..48744bea 100755 --- a/API.md +++ b/API.md @@ -3068,7 +3068,7 @@ Requires the string value to be a valid [RFC 3986](http://tools.ietf.org/html/rf - `allowRelative` - Allow relative URIs. Defaults to `false`. - `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). + - `domain` - Validate the domain component using the options specified in [`string.domain()`](#stringdomainoptions). An extra option `hostname` can be added to validate specific hostnames. It supports a string, a regular expression or an array containing multiple of them. ```js // Accept git or git http/https @@ -3080,7 +3080,7 @@ const schema = Joi.string().uri({ }); ``` -Possible validation errors: [`string.uri`](#stringuri), [`string.uriCustomScheme`](#stringuricustomscheme), [`string.uriRelativeOnly`](#stringurirelativeonly), [`string.domain`](#stringdomain) +Possible validation errors: [`string.uri`](#stringuri), [`string.uriCustomScheme`](#stringuricustomscheme), [`string.uriRelativeOnly`](#stringurirelativeonly), [`string.domain`](#stringdomain), [`string.uri.hostname`](#stringurihostname) ### `symbol` @@ -4395,6 +4395,10 @@ The string isn't all upper-cased. The string isn't a valid URI. +#### `string.uri.hostname` + +The hostname isn't a match for any of the given hostname strings or regular expressions. + #### `string.uriCustomScheme` The string isn't a valid URI considering the custom schemes. From 06483be333a87c9306207010f1fca59a38b444b8 Mon Sep 17 00:00:00 2001 From: Elias Trizotti Date: Fri, 14 Oct 2022 13:51:47 -0300 Subject: [PATCH 09/14] Refactorarray check --- lib/types/string.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/types/string.js b/lib/types/string.js index 8f0c4741..f9e78c7e 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -689,18 +689,9 @@ module.exports = Any.extend({ if (Array.isArray(domain.hostname)) { - let matches = 0; - domain.hostname.forEach((matcher) => { - - if (isMatch(matcher)) { - matches++; - } - }); - - if (matches === 0) { + if (!domain.hostname.some(isMatch)) { return helpers.error('string.uri.hostname', { value: matched, expected: 'any of the hostname items' }); } - } } } From 5c2a0000c305041e78d802f2528bc7f1f20316bf Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 17 Oct 2022 13:34:57 -0300 Subject: [PATCH 10/14] Add test and clean up the code --- lib/types/string.js | 6 +----- test/types/string.js | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/types/string.js b/lib/types/string.js index f9e78c7e..2d4df74a 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -660,7 +660,6 @@ module.exports = Any.extend({ if ((!options.allowRelative || matched) && !Domain.isValid(matched, domain)) { - return helpers.error('string.domain', { value: matched }); } @@ -672,11 +671,8 @@ module.exports = Any.extend({ return matcher.test(matched); } - if (typeof matcher === 'string') { - return matcher === matched; - } + return matcher === matched; - return false; }; if (typeof domain.hostname === 'string' || domain.hostname instanceof RegExp) { diff --git a/test/types/string.js b/test/types/string.js index dba88f91..7bfac211 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -8630,6 +8630,9 @@ describe('string', () => { ['https://dummy.org/test', true], ['https://dummy.com/test', true] ]); + + const numberSchema = Joi.string().uri({ scheme: 'https', domain: { hostname: 1 } }); + expect(numberSchema.validate(null).error.annotate()).to.equal('"value" must be a string'); }); }); From 45c32ee93740d15340fb35e6e096b3f072af5ea6 Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 17 Oct 2022 13:41:14 -0300 Subject: [PATCH 11/14] Fix typo --- API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API.md b/API.md index 48744bea..858aa480 100755 --- a/API.md +++ b/API.md @@ -3068,7 +3068,7 @@ Requires the string value to be a valid [RFC 3986](http://tools.ietf.org/html/rf - `allowRelative` - Allow relative URIs. Defaults to `false`. - `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). An extra option `hostname` can be added to validate specific hostnames. It supports a string, a regular expression or an array containing multiple of them. + - `domain` - Validate the domain component using the options specified in [`string.domain()`](#stringdomainoptions). An extra option `hostname` can be added to validate specific hostnames. It supports a string, a regular expression or an array containing multiples of them. ```js // Accept git or git http/https From 1a9f9a58cfa710ceabf1a27cd6f8817b802b8ce7 Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 17 Oct 2022 16:24:48 -0300 Subject: [PATCH 12/14] Remove unnecessary test --- test/types/string.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/types/string.js b/test/types/string.js index 7bfac211..c3dc8130 100755 --- a/test/types/string.js +++ b/test/types/string.js @@ -8631,8 +8631,6 @@ describe('string', () => { ['https://dummy.com/test', true] ]); - const numberSchema = Joi.string().uri({ scheme: 'https', domain: { hostname: 1 } }); - expect(numberSchema.validate(null).error.annotate()).to.equal('"value" must be a string'); }); }); From 53de422908879065a7b2af1eaa302f2d82bc1379 Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 24 Oct 2022 10:23:55 -0300 Subject: [PATCH 13/14] Refactoring --- lib/types/string.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/types/string.js b/lib/types/string.js index 2d4df74a..a0cc467d 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -675,20 +675,19 @@ module.exports = Any.extend({ }; - if (typeof domain.hostname === 'string' || domain.hostname instanceof RegExp) { - - if (!isMatch(domain.hostname)) { - return helpers.error('string.uri.hostname', { value: matched, expected: String(domain.hostname) }); - } - - } - if (Array.isArray(domain.hostname)) { if (!domain.hostname.some(isMatch)) { return helpers.error('string.uri.hostname', { value: matched, expected: 'any of the hostname items' }); } } + else { + + if (!isMatch(domain.hostname)) { + return helpers.error('string.uri.hostname', { value: matched, expected: String(domain.hostname) }); + } + + } } } From d74362c74634c59e813bc7acda2b1926e30f8a1f Mon Sep 17 00:00:00 2001 From: trizotti Date: Mon, 24 Oct 2022 10:38:29 -0300 Subject: [PATCH 14/14] Remove unnecessary type conversion --- 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 a0cc467d..49103d85 100755 --- a/lib/types/string.js +++ b/lib/types/string.js @@ -684,7 +684,7 @@ module.exports = Any.extend({ else { if (!isMatch(domain.hostname)) { - return helpers.error('string.uri.hostname', { value: matched, expected: String(domain.hostname) }); + return helpers.error('string.uri.hostname', { value: matched, expected: domain.hostname }); } }