diff --git a/lib/language.js b/lib/language.js index 88f5f1fbb..bfdec31e8 100755 --- a/lib/language.js +++ b/lib/language.js @@ -126,6 +126,7 @@ exports.errors = { }, email: 'must be a valid email', uri: 'must be a valid uri', + uriRelativeOnly: 'must be a relative only uri', uriCustomScheme: 'must be a valid uri with a scheme matching the {{scheme}} pattern', isoDate: 'must be a valid ISO 8601 date', guid: 'must be a valid GUID', diff --git a/lib/string.js b/lib/string.js index 359c90f07..2a35fecf3 100755 --- a/lib/string.js +++ b/lib/string.js @@ -224,6 +224,7 @@ internals.String = class extends Any { let customScheme = ''; let allowRelative = false; + let relativeOnly = false; let regex = internals.uriRegex; if (uriOptions) { @@ -260,10 +261,14 @@ internals.String = class extends Any { if (uriOptions.allowRelative) { allowRelative = true; } + + if (uriOptions.relativeOnly) { + relativeOnly = true; + } } - if (customScheme || allowRelative) { - regex = Uri.createUriRegex(customScheme, allowRelative); + if (customScheme || allowRelative || relativeOnly) { + regex = Uri.createUriRegex(customScheme, allowRelative, relativeOnly); } return this._test('uri', uriOptions, function (value, state, options) { @@ -272,6 +277,10 @@ internals.String = class extends Any { return value; } + if (relativeOnly) { + return this.createError('string.uriRelativeOnly', { value }, state, options); + } + if (customScheme) { return this.createError('string.uriCustomScheme', { scheme: customScheme, value }, state, options); } diff --git a/lib/string/uri.js b/lib/string/uri.js index fd9ce6571..f5622c505 100755 --- a/lib/string/uri.js +++ b/lib/string/uri.js @@ -9,19 +9,26 @@ const RFC3986 = require('./rfc3986'); const internals = { Uri: { - createUriRegex: function (optionalScheme, allowRelative) { + createUriRegex: function (optionalScheme, allowRelative, relativeOnly) { let scheme = RFC3986.scheme; + let prefix; - // If we were passed a scheme, use it instead of the generic one - if (optionalScheme) { - - // Have to put this in a non-capturing group to handle the OR statements - scheme = '(?:' + optionalScheme + ')'; + if (relativeOnly) { + prefix = '(?:' + RFC3986.relativeRef + ')'; } + else { + // If we were passed a scheme, use it instead of the generic one + if (optionalScheme) { + + // Have to put this in a non-capturing group to handle the OR statements + scheme = '(?:' + optionalScheme + ')'; + } - const withScheme = '(?:' + scheme + ':' + RFC3986.hierPart + ')'; - const prefix = allowRelative ? '(?:' + withScheme + '|' + RFC3986.relativeRef + ')' : withScheme; + const withScheme = '(?:' + scheme + ':' + RFC3986.hierPart + ')'; + + prefix = allowRelative ? '(?:' + withScheme + '|' + RFC3986.relativeRef + ')' : withScheme; + } /** * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] diff --git a/test/string.js b/test/string.js index 58d37e187..72bed3788 100755 --- a/test/string.js +++ b/test/string.js @@ -1847,6 +1847,110 @@ describe('string', () => { ['/absolute', true] ], done); }); + + it('validates relative only uri', (done) => { + + const schema = Joi.string().uri({ relativeOnly: true }); + Helper.validate(schema, [ + ['foo://example.com:8042/over/there?name=ferret#nose', false, null, '"value" must be a relative only uri'], + ['urn:example:animal:ferret:nose', false, null, '"value" must be a relative only uri'], + ['ftp://ftp.is.co.za/rfc/rfc1808.txt', false, null, '"value" must be a relative only uri'], + ['http://www.ietf.org/rfc/rfc2396.txt', false, null, '"value" must be a relative only uri'], + ['ldap://[2001:db8::7]/c=GB?objectClass?one', false, null, '"value" must be a relative only uri'], + ['mailto:John.Doe@example.com', false, null, '"value" must be a relative only uri'], + ['news:comp.infosystems.www.servers.unix', false, null, '"value" must be a relative only uri'], + ['tel:+1-816-555-1212', false, null, '"value" must be a relative only uri'], + ['telnet://192.0.2.16:80/', false, null, '"value" must be a relative only uri'], + ['urn:oasis:names:specification:docbook:dtd:xml:4.1.2', false, null, '"value" must be a relative only uri'], + ['file:///example.txt', false, null, '"value" must be a relative only uri'], + ['http://asdf:qw%20er@localhost:8000?asdf=12345&asda=fc%2F#bacon', false, null, '"value" must be a relative only uri'], + ['http://asdf@localhost:8000', false, null, '"value" must be a relative only uri'], + ['http://[v1.09azAZ-._~!$&\'()*+,;=:]', false, null, '"value" must be a relative only uri'], + ['http://[a:b:c:d:e::1.2.3.4]', false, null, '"value" must be a relative only uri'], + ['coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]', false, null, '"value" must be a relative only uri'], + ['http://[1080:0:0:0:8:800:200C:417A]', false, null, '"value" must be a relative only uri'], + ['http://127.0.0.1:8000/foo?bar', false, null, '"value" must be a relative only uri'], + ['http://asdf:qwer@localhost:8000', false, null, '"value" must be a relative only uri'], + ['http://user:pass%3A@localhost:80', false, null, '"value" must be a relative only uri'], + ['http://localhost:123', false, null, '"value" must be a relative only uri'], + ['https://localhost:123', false, null, '"value" must be a relative only uri'], + ['file:///whatever', false, null, '"value" must be a relative only uri'], + ['mailto:asdf@asdf.com', false, null, '"value" must be a relative only uri'], + ['ftp://www.example.com', false, null, '"value" must be a relative only uri'], + ['javascript:alert(\'hello\');', false, null, '"value" must be a relative only uri'], // eslint-disable-line no-script-url + ['xmpp:isaacschlueter@jabber.org', false, null, '"value" must be a relative only uri'], + ['f://some.host/path', false, null, '"value" must be a relative only uri'], + ['http://localhost:18/asdf', false, null, '"value" must be a relative only uri'], + ['http://localhost:42/asdf?qwer=zxcv', false, null, '"value" must be a relative only uri'], + ['HTTP://www.example.com/', false, null, '"value" must be a relative only uri'], + ['HTTP://www.example.com', false, null, '"value" must be a relative only uri'], + ['http://www.ExAmPlE.com/', false, null, '"value" must be a relative only uri'], + ['http://user:pw@www.ExAmPlE.com/', false, null, '"value" must be a relative only uri'], + ['http://USER:PW@www.ExAmPlE.com/', false, null, '"value" must be a relative only uri'], + ['http://user@www.example.com/', false, null, '"value" must be a relative only uri'], + ['http://user%3Apw@www.example.com/', false, null, '"value" must be a relative only uri'], + ['http://x.com/path?that%27s#all,%20folks', false, null, '"value" must be a relative only uri'], + ['HTTP://X.COM/Y', false, null, '"value" must be a relative only uri'], + ['http://www.narwhaljs.org/blog/categories?id=news', false, null, '"value" must be a relative only uri'], + ['http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', false, null, '"value" must be a relative only uri'], + ['http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', false, null, '"value" must be a relative only uri'], + ['http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', false, null, '"value" must be a relative only uri'], + ['http://_jabber._tcp.google.com:80/test', false, null, '"value" must be a relative only uri'], + ['http://user:pass@_jabber._tcp.google.com:80/test', false, null, '"value" must be a relative only uri'], + ['http://[fe80::1]/a/b?a=b#abc', false, null, '"value" must be a relative only uri'], + ['http://user:password@[3ffe:2a00:100:7031::1]:8080', false, null, '"value" must be a relative only uri'], + ['coap://[1080:0:0:0:8:800:200C:417A]:61616/', false, null, '"value" must be a relative only uri'], + ['git+http://github.com/joyent/node.git', false, null, '"value" must be a relative only uri'], + ['http://bucket_name.s3.amazonaws.com/image.jpg', false, null, '"value" must be a relative only uri'], + ['dot.test://foo/bar', false, null, '"value" must be a relative only uri'], + ['svn+ssh://foo/bar', false, null, '"value" must be a relative only uri'], + ['dash-test://foo/bar', false, null, '"value" must be a relative only uri'], + ['xmpp:isaacschlueter@jabber.org', false, null, '"value" must be a relative only uri'], + ['http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar', false, null, '"value" must be a relative only uri'], + ['javascript:alert(\'hello\');', false, null, '"value" must be a relative only uri'], // eslint-disable-line no-script-url + ['file://localhost/etc/node/', false, null, '"value" must be a relative only uri'], + ['file:///etc/node/', false, null, '"value" must be a relative only uri'], + ['http://USER:PW@www.ExAmPlE.com/', false, null, '"value" must be a relative only uri'], + ['mailto:local1@domain1?query1', false, null, '"value" must be a relative only uri'], + ['http://example/a/b?c/../d', false, null, '"value" must be a relative only uri'], + ['http://example/x%2Fabc', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/d;p=1/g;x=1/y', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/g#s/../x', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/.foo', false, null, '"value" must be a relative only uri'], + ['http://example.com/b//c//d;p?q#blarg', false, null, '"value" must be a relative only uri'], + ['g:h', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/g', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/g/', false, null, '"value" must be a relative only uri'], + ['http://a/g', false, null, '"value" must be a relative only uri'], + ['http://g', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/d;p?y', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/g?y', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/d;p?q#s', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/g#s', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/g?y#s', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/;x', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/g;x', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/g;x?y#s', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/d;p?q', false, null, '"value" must be a relative only uri'], + ['http://a/b/c/', false, null, '"value" must be a relative only uri'], + ['http://a/b/', false, null, '"value" must be a relative only uri'], + ['http://a/b/g', false, null, '"value" must be a relative only uri'], + ['http://a/', false, null, '"value" must be a relative only uri'], + ['http://a/g', false, null, '"value" must be a relative only uri'], + ['http://a/g', false, null, '"value" must be a relative only uri'], + ['file:/asda', false, null, '"value" must be a relative only uri'], + ['qwerty', true], + ['invalid uri', false, null, '"value" must be a relative only uri'], + ['1http://google.com', false, null, '"value" must be a relative only uri'], + ['http://testdomain`,.<>/?\'";{}][++\\|~!@#$%^&*().org', false, null, '"value" must be a relative only uri'], + ['', false, null, '"value" is not allowed to be empty'], + ['(╯°□°)╯︵ ┻━┻', false, null, '"value" must be a relative only uri'], + ['one/two/three?value=abc&value2=123#david-rules', true], + ['//username:password@test.example.com/one/two/three?value=abc&value2=123#david-rules', true], + ['http://a\r" \t\n<\'b:b@c\r\nd/e?f', false, null, '"value" must be a relative only uri'], + ['/absolute', true] + ], done); + }); }); describe('truncate()', () => {