Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate hostname in uri #2846

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
8 changes: 6 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 multiples of them.

```js
// Accept git or git http/https
Expand All @@ -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`

Expand Down Expand Up @@ -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.
Expand Down
40 changes: 35 additions & 5 deletions lib/types/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is going to be part of domain, you should probably exclude it from the call to isValid to avoid any possible side-effect from an evolution of that module.

}

const { regex, scheme } = Uri.regex(options);
Expand All @@ -655,11 +655,40 @@ 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)) {
Copy link
Contributor Author

@trizotti trizotti Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call to isValid should be avoided? @Marsup

return helpers.error('string.domain', { value: matched });
}

if (domain.hostname) {

const isMatch = (matcher) => {

if (matcher instanceof RegExp) {
return matcher.test(matched);
}

return matcher === matched;

};

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: domain.hostname });
}

}
}
}

return value;
Expand Down Expand Up @@ -721,6 +750,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}} 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'
Expand Down
26 changes: 26 additions & 0 deletions test/types/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -8606,6 +8606,32 @@ describe('string', () => {

expect(() => Joi.string().uri({ foo: 'bar', baz: 'qux' })).to.throw('Options contain unknown keys: foo,baz');
});

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" does not match example.com'],
['https://test.com/example', false, '"value" does not match example.com']
]);

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" does not match any of the hostname items'],
['https://dummy.org/test', true],
['https://dummy.com/test', true]
]);

});
});

describe('valid()', () => {
Expand Down