From 8da402ebd7ae9c34fdb5513c7290b2385df79025 Mon Sep 17 00:00:00 2001 From: Tedy Fazrin Date: Thu, 22 Oct 2026 11:16:43 +0700 Subject: [PATCH] feat(isURL): add option to require protocol slashes --- README.md | 2 +- src/lib/isURL.js | 9 ++++- test/validators.test.js | 73 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8b7aedd9..4028e7761 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Validator | Description **isStrongPassword(str [, options])** | check if the string can be considered a strong password or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.
Default options:
`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }` **isTime(str [, options])** | check if the string is a valid time e.g. [`23:01:59`, new Date().toLocaleTimeString()].

`options` is an object which can contain the keys `hourFormat` or `mode`.

`hourFormat` is a key and defaults to `'hour24'`.

`mode` is a key and defaults to `'default'`.

`hourFormat` can contain the values `'hour12'` or `'hour24'`, `'hour24'` will validate hours in 24 format and `'hour12'` will validate hours in 12 format.

`mode` can contain the values `'default', 'withSeconds', withOptionalSeconds`, `'default'` will validate `HH:MM` format, `'withSeconds'` will validate the `HH:MM:SS` format, `'withOptionalSeconds'` will validate `'HH:MM'` and `'HH:MM:SS'` formats. **isTaxID(str, locale)** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.

More info about exact TIN support can be found in `src/lib/isTaxID.js`.

Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-IN', 'en-US', 'es-AR', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV', 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE', 'uk-UA']`. -**isURL(str [, options])** | check if the string is a URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.

`protocols` - valid protocols can be modified with this option.
`require_tld` - If set to false isURL will not check if the URL's host includes a top-level domain.
`require_protocol` - **RECOMMENDED** if set to true isURL will return false if protocol is not present in the URL. Without this setting, some malicious URLs cannot be distinguishable from a valid URL with authentication information.
`require_host` - if set to false isURL will not check if host is present in the URL.
`require_port` - if set to true isURL will check if port is present in the URL.
`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.
`allow_underscores` - if set to true, the validator will allow underscores in the URL.
`host_whitelist` - if set to an array of strings or regexp, and the domain matches none of the strings defined in it, the validation fails.
`host_blacklist` - if set to an array of strings or regexp, and the domain matches any of the strings defined in it, the validation fails.
`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.
`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.
`allow_fragments` - if set to false isURL will return false if fragments are present.
`allow_query_components` - if set to false isURL will return false if query components are present.
`disallow_auth` - if set to true, the validator will fail if the URL contains an authentication component, e.g. `http://username:password@example.com`.
`validate_length` - if set to false isURL will skip string length validation. `max_allowed_length` will be ignored if this is set as `false`.
`max_allowed_length` - if set, isURL will not allow URLs longer than the specified value (default is 2084 that IE maximum URL length).
+**isURL(str [, options])** | check if the string is a URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_protocol_slashes: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.

`protocols` - valid protocols can be modified with this option.
`require_tld` - If set to false isURL will not check if the URL's host includes a top-level domain.
`require_protocol` - **RECOMMENDED** if set to true isURL will return false if protocol is not present in the URL. Without this setting, some malicious URLs cannot be distinguishable from a valid URL with authentication information.
`require_protocol_slashes` - if set to true, an explicit protocol must be followed by `//` (for example, `https://example.com` passes and `https:example.com` fails). This does not require a protocol; use `require_protocol: true` for that.
`require_host` - if set to false isURL will not check if host is present in the URL.
`require_port` - if set to true isURL will check if port is present in the URL.
`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.
`allow_underscores` - if set to true, the validator will allow underscores in the URL.
`host_whitelist` - if set to an array of strings or regexp, and the domain matches none of the strings defined in it, the validation fails.
`host_blacklist` - if set to an array of strings or regexp, and the domain matches any of the strings defined in it, the validation fails.
`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.
`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.
`allow_fragments` - if set to false isURL will return false if fragments are present.
`allow_query_components` - if set to false isURL will return false if query components are present.
`disallow_auth` - if set to true, the validator will fail if the URL contains an authentication component, e.g. `http://username:password@example.com`.
`validate_length` - if set to false isURL will skip string length validation. `max_allowed_length` will be ignored if this is set as `false`.
`max_allowed_length` - if set, isURL will not allow URLs longer than the specified value (default is 2084 that IE maximum URL length).
**isULID(str)** | check if the string is a [ULID](https://github.com/ulid/spec). **isUUID(str [, version])** | check if the string is an RFC9562 UUID.
`version` is one of `'1'`-`'8'`, `'nil'`, `'max'`, `'all'` or `'loose'`. The `'loose'` option checks if the string is a UUID-like string with hexadecimal values, ignoring RFC9565. **isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars. diff --git a/src/lib/isURL.js b/src/lib/isURL.js index 600a91dec..cd97412e4 100644 --- a/src/lib/isURL.js +++ b/src/lib/isURL.js @@ -12,6 +12,7 @@ options for isURL method protocols - valid protocols can be modified with this option. require_tld - If set to false isURL will not check if the URL's host includes a top-level domain. require_protocol - if set to true isURL will return false if protocol is not present in the URL. +require_protocol_slashes - if set to true, an explicit protocol must be followed by `//`. require_host - if set to false isURL will not check if host is present in the URL. require_port - if set to true isURL will check if port is present in the URL. require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option. @@ -39,6 +40,7 @@ const default_url_options = { protocols: ['http', 'https', 'ftp'], require_tld: true, require_protocol: false, + require_protocol_slashes: false, require_host: true, require_port: false, require_valid_protocol: true, @@ -91,6 +93,11 @@ export default function isURL(url, options) { let had_explicit_protocol = false; const cleanUpProtocol = (potential_protocol) => { + const after_protocol = url.substring(protocol_match[0].length); + if (options.require_protocol_slashes && after_protocol.slice(0, 2) !== '//') { + return false; + } + had_explicit_protocol = true; protocol = potential_protocol.toLowerCase(); @@ -100,7 +107,7 @@ export default function isURL(url, options) { } // Remove the protocol from the URL string. - return url.substring(protocol_match[0].length); + return after_protocol; }; if (protocol_match) { diff --git a/test/validators.test.js b/test/validators.test.js index 98d2a12ff..3027be1e3 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -563,6 +563,79 @@ describe('Validators', () => { }); }); + it('should require slashes after an explicit protocol when requested', () => { + test({ + validator: 'isURL', + args: [{ require_protocol_slashes: true }], + valid: [ + 'http://example.com', + 'HTTPS://example.com/path?query=value#fragment', + 'ftp://example.com', + 'https://user:password@example.com', + 'https://[::1]:8080/path', + 'example.com:8080/path', + 'user:password@example.com', + ], + invalid: [ + 'http:example.com', + 'https:example.com', + 'HTTPS:example.com/path?query=value#fragment', + 'ftp:example.com', + 'https:/example.com', + '//example.com', + ], + }); + }); + + it('should combine protocol slashes with requiring a protocol', () => { + test({ + validator: 'isURL', + args: [{ require_protocol_slashes: true, require_protocol: true }], + valid: ['https://example.com'], + invalid: ['https:example.com', 'example.com', 'user:password@example.com'], + }); + }); + + it('should allow protocol-relative URLs when only protocol slashes are required', () => { + test({ + validator: 'isURL', + args: [{ require_protocol_slashes: true, allow_protocol_relative_urls: true }], + valid: ['//example.com', 'example.com', 'https://example.com'], + invalid: ['https:example.com'], + }); + }); + + it('should require slashes for custom protocols when requested', () => { + test({ + validator: 'isURL', + args: [{ require_protocol_slashes: true, protocols: ['custom'] }], + valid: ['custom://example.com'], + invalid: ['custom:example.com'], + }); + test({ + validator: 'isURL', + args: [{ require_protocol_slashes: true, require_valid_protocol: false }], + valid: ['custom://example.com'], + invalid: ['custom:example.com'], + }); + }); + + it('should keep accepting protocols without slashes by default', () => { + const optionsList = [ + undefined, + { require_protocol_slashes: false }, + { require_protocol: true }, + ]; + optionsList.forEach((options) => { + test({ + validator: 'isURL', + args: [options], + valid: ['http:example.com', 'https:example.com', 'ftp:example.com'], + invalid: [], + }); + }); + }); + it('should validate file URLs without a host', () => { test({ validator: 'isURL',