From 4af61243ba0ae93f29e7689040e188b5849ff1b0 Mon Sep 17 00:00:00 2001 From: Rik Smale <13023439+WikiRik@users.noreply.github.com> Date: Sun, 26 Oct 2025 07:37:57 +0100 Subject: [PATCH 1/5] maintenance: 2510 release (#2585) Co-authored-by: Rubin Bhandari From cf401458b8733d981a3724d634c795a9d612b516 Mon Sep 17 00:00:00 2001 From: Marc Bernard <59966492+mbtools@users.noreply.github.com> Date: Tue, 4 Nov 2025 04:44:37 -0500 Subject: [PATCH 2/5] fix: URL validation for hostnames with ports (no protocol) (#2622) --- src/lib/isURL.js | 21 +++++++++++++--- test/validators.test.js | 56 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/lib/isURL.js b/src/lib/isURL.js index 8ae971ed6..b55f8e031 100644 --- a/src/lib/isURL.js +++ b/src/lib/isURL.js @@ -142,11 +142,24 @@ export default function isURL(url, options) { } } } else { - // No @ symbol, this is definitely a protocol - url = cleanUpProtocol(potential_protocol); + // No @ symbol found. Check if this could be a port number instead of a protocol. + // If what's after the colon is numeric (or starts with a digit and contains only + // valid port characters until a path separator), it's likely hostname:port, not a protocol. + const looks_like_port = /^[0-9]/.test(after_colon); - if (url === false) { - return false; + if (looks_like_port) { + // This looks like hostname:port, not a protocol + if (options.require_protocol) { + return false; + } + // Don't consume anything; let it be parsed as hostname:port + } else { + // This is definitely a protocol + url = cleanUpProtocol(potential_protocol); + + if (url === false) { + return false; + } } } } else { diff --git a/test/validators.test.js b/test/validators.test.js index a3c5f5a5d..5196c6fe9 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -488,6 +488,62 @@ describe('Validators', () => { }); }); + it('should validate URLs without protocol', () => { + test({ + validator: 'isURL', + args: [{ + require_tld: false, + require_valid_protocol: false, + }], + valid: [ + 'localhost', + 'localhost:3000', + 'service-name:8080', + 'https://localhost', + 'http://localhost:3000', + 'http://service-name:8080', + 'user:password@localhost', + 'user:pass@service-name:8080', + ], + invalid: [], + }); + + // Test with require_protocol: true - should reject hostnames with ports but no protocol + test({ + validator: 'isURL', + args: [{ + require_tld: false, + require_protocol: true, + require_valid_protocol: false, + }], + valid: [ + 'http://localhost:3000', + 'https://service-name:8080', + 'custom://localhost', + ], + invalid: [ + 'localhost:3000', + 'service-name:8080', + 'user:password@localhost', + ], + }); + + // Test non-numeric patterns after colon (should be treated as protocols) + test({ + validator: 'isURL', + args: [{ + require_tld: false, + require_valid_protocol: false, + protocols: ['custom', 'myscheme'], + }], + valid: [ + 'custom:something', + 'myscheme:data', + ], + invalid: [], + }); + }); + it('should validate URLs with custom protocols', () => { test({ validator: 'isURL', From f2e3633f22fd3016656789d100bc451d857e7488 Mon Sep 17 00:00:00 2001 From: Marc Bernard <59966492+mbtools@users.noreply.github.com> Date: Tue, 4 Nov 2025 04:44:45 -0500 Subject: [PATCH 3/5] docs: add install instructions to contibution guide (#2621) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7da04163..747a30ea2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,7 @@ In general, we follow the "fork-and-pull" Git workflow. 1. [Fork](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) the repository on GitHub 2. Clone the project to your local machine 3. Work on your fork + * Install the project using `npm install --legacy-peer-deps` (see [issue](https://github.com/validatorjs/validator.js/issues/2123)) * Make your changes and additions - Most of your changes should be focused on src/ and test/ folders and/or [README.md](https://github.com/validatorjs/validator.js/blob/master/README.md). - Files such as validator.js, validator.min.js and files in lib/ folder are autogenerated when running tests (npm test) and need not to be changed **manually**. From d457ecaf55b0f3d8bd379d82757425d0d13dd382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Wr=C3=B3tniak?= Date: Wed, 5 Nov 2025 18:34:47 +0100 Subject: [PATCH 4/5] fix(isLength): correctly handle Unicode variation selectors (#2616) --- src/lib/isLength.js | 2 +- test/validators.test.js | 97 --------------------- test/validators/isLength.test.js | 144 +++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 98 deletions(-) create mode 100644 test/validators/isLength.test.js diff --git a/src/lib/isLength.js b/src/lib/isLength.js index 4d5d52546..56d8e3fbf 100644 --- a/src/lib/isLength.js +++ b/src/lib/isLength.js @@ -14,7 +14,7 @@ export default function isLength(str, options) { max = arguments[2]; } - const presentationSequences = str.match(/(\uFE0F|\uFE0E)/g) || []; + const presentationSequences = str.match(/[^\uFE0F\uFE0E][\uFE0F\uFE0E]/g) || []; const surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g) || []; const len = str.length - presentationSequences.length - surrogatePairs.length; const isInsideRange = len >= min && (typeof max === 'undefined' || len <= max); diff --git a/test/validators.test.js b/test/validators.test.js index 5196c6fe9..c5ea4dc99 100644 --- a/test/validators.test.js +++ b/test/validators.test.js @@ -5591,32 +5591,6 @@ describe('Validators', () => { }); }); - it('should validate strings by length (deprecated api)', () => { - test({ - validator: 'isLength', - args: [2], - valid: ['abc', 'de', 'abcd'], - invalid: ['', 'a'], - }); - test({ - validator: 'isLength', - args: [2, 3], - valid: ['abc', 'de'], - invalid: ['', 'a', 'abcd'], - }); - test({ - validator: 'isLength', - args: [2, 3], - valid: ['干𩸽', '𠮷野家'], - invalid: ['', '𠀋', '千竈通り'], - }); - test({ - validator: 'isLength', - args: [0, 0], - valid: [''], - invalid: ['a', 'ab'], - }); - }); it('should validate isLocale codes', () => { test({ @@ -5695,77 +5669,6 @@ describe('Validators', () => { }); }); - it('should validate strings by length', () => { - test({ - validator: 'isLength', - args: [{ min: 2 }], - valid: ['abc', 'de', 'abcd'], - invalid: ['', 'a'], - }); - test({ - validator: 'isLength', - args: [{ min: 2, max: 3 }], - valid: ['abc', 'de'], - invalid: ['', 'a', 'abcd'], - }); - test({ - validator: 'isLength', - args: [{ min: 2, max: 3 }], - valid: ['干𩸽', '𠮷野家'], - invalid: ['', '𠀋', '千竈通り'], - }); - test({ - validator: 'isLength', - args: [{ max: 3 }], - valid: ['abc', 'de', 'a', ''], - invalid: ['abcd'], - }); - test({ - validator: 'isLength', - args: [{ max: 6, discreteLengths: 5 }], - valid: ['abcd', 'vfd', 'ff', '', 'k'], - invalid: ['abcdefgh', 'hfjdksks'], - }); - test({ - validator: 'isLength', - args: [{ min: 2, max: 6, discreteLengths: 5 }], - valid: ['bsa', 'vfvd', 'ff'], - invalid: ['', ' ', 'hfskdunvc'], - }); - test({ - validator: 'isLength', - args: [{ min: 1, discreteLengths: 2 }], - valid: [' ', 'hello', 'bsa'], - invalid: [''], - }); - test({ - validator: 'isLength', - args: [{ max: 0 }], - valid: [''], - invalid: ['a', 'ab'], - }); - test({ - validator: 'isLength', - args: [{ min: 5, max: 10, discreteLengths: [2, 6, 8, 9] }], - valid: ['helloguy', 'shopping', 'validator', 'length'], - invalid: ['abcde', 'abcdefg'], - }); - test({ - validator: 'isLength', - args: [{ discreteLengths: '9' }], - valid: ['a', 'abcd', 'abcdefghijkl'], - invalid: [], - }); - test({ - validator: 'isLength', - valid: ['a', '', 'asds'], - }); - test({ - validator: 'isLength', - args: [{ max: 8 }], - valid: ['👩🦰👩👩👦👦🏳️🌈', '⏩︎⏩︎⏪︎⏪︎⏭︎⏭︎⏮︎⏮︎'], - }); - }); it('should validate strings by byte length', () => { test({ diff --git a/test/validators/isLength.test.js b/test/validators/isLength.test.js new file mode 100644 index 000000000..5c1444002 --- /dev/null +++ b/test/validators/isLength.test.js @@ -0,0 +1,144 @@ +import test from '../testFunctions'; + +describe('isLength', () => { + it('should return false for a string with length greater than the max', () => { + test({ + validator: 'isLength', + args: [{ max: 3 }], + invalid: ['test'], + }); + }); + + it('should return true for a string with length equal to the max', () => { + test({ + validator: 'isLength', + args: [{ max: 4 }], + valid: ['test'], + }); + }); + + it('should correctly calculate the length of a string with presentation sequences', () => { + test({ + validator: 'isLength', + args: [{ max: 4 }], + valid: ['test\uFE0F'], + }); + + test({ + validator: 'isLength', + args: [{ min: 5, max: 5 }], + valid: ['test\uFE0F\uFE0F'], + }); + + test({ + validator: 'isLength', + args: [{ min: 5, max: 5 }], + valid: ['\uFE0Ftest'], + }); + + test({ + validator: 'isLength', + args: [{ min: 9, max: 9 }], + valid: ['test\uFE0F\uFE0F\uFE0F\uFE0F\uFE0F\uFE0F'], + }); + }); + + it('should validate strings by length (deprecated api)', () => { + test({ + validator: 'isLength', + args: [2], + valid: ['abc', 'de', 'abcd'], + invalid: ['', 'a'], + }); + test({ + validator: 'isLength', + args: [2, 3], + valid: ['abc', 'de'], + invalid: ['', 'a', 'abcd'], + }); + test({ + validator: 'isLength', + args: [2, 3], + valid: ['干𩸽', '𠮷野家'], + invalid: ['', '𠀋', '千竈通り'], + }); + test({ + validator: 'isLength', + args: [0, 0], + valid: [''], + invalid: ['a', 'ab'], + }); + }); + + it('should validate strings by length', () => { + test({ + validator: 'isLength', + args: [{ min: 2 }], + valid: ['abc', 'de', 'abcd'], + invalid: ['', 'a'], + }); + test({ + validator: 'isLength', + args: [{ min: 2, max: 3 }], + valid: ['abc', 'de'], + invalid: ['', 'a', 'abcd'], + }); + test({ + validator: 'isLength', + args: [{ min: 2, max: 3 }], + valid: ['干𩸽', '𠮷野家'], + invalid: ['', '𠀋', '千竈通り'], + }); + test({ + validator: 'isLength', + args: [{ max: 3 }], + valid: ['abc', 'de', 'a', ''], + invalid: ['abcd'], + }); + test({ + validator: 'isLength', + args: [{ max: 6, discreteLengths: 5 }], + valid: ['abcd', 'vfd', 'ff', '', 'k'], + invalid: ['abcdefgh', 'hfjdksks'], + }); + test({ + validator: 'isLength', + args: [{ min: 2, max: 6, discreteLengths: 5 }], + valid: ['bsa', 'vfvd', 'ff'], + invalid: ['', ' ', 'hfskdunvc'], + }); + test({ + validator: 'isLength', + args: [{ min: 1, discreteLengths: 2 }], + valid: [' ', 'hello', 'bsa'], + invalid: [''], + }); + test({ + validator: 'isLength', + args: [{ max: 0 }], + valid: [''], + invalid: ['a', 'ab'], + }); + test({ + validator: 'isLength', + args: [{ min: 5, max: 10, discreteLengths: [2, 6, 8, 9] }], + valid: ['helloguy', 'shopping', 'validator', 'length'], + invalid: ['abcde', 'abcdefg'], + }); + test({ + validator: 'isLength', + args: [{ discreteLengths: '9' }], + valid: ['a', 'abcd', 'abcdefghijkl'], + invalid: [], + }); + test({ + validator: 'isLength', + valid: ['a', '', 'asds'], + }); + test({ + validator: 'isLength', + args: [{ max: 8 }], + valid: ['👩🦰👩👩👦👦🏳️🌈', '⏩︎⏩︎⏪︎⏪︎⏭︎⏭︎⏮︎⏮︎'], + }); + }); +}); From f2b5c17dbe03f2ca9ad9122c597f81e86ce1a9a1 Mon Sep 17 00:00:00 2001 From: Rik Smale <13023439+WikiRik@users.noreply.github.com> Date: Sat, 8 Nov 2025 13:54:51 +0100 Subject: [PATCH 5/5] maintenance: 2511 release (#2627) --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- src/index.js | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25192b24a..4973389bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 13.15.22 + +### Fixes, New Locales and Enhancements + +- [#2622](https://github.com/validatorjs/validator.js/pull/2622) `isURL`: fix regression with hostnames with ports @mbtools +- [#2616](https://github.com/validatorjs/validator.js/pull/2616) `isLength`: improve handling Unicode variation selectors @koral-- +- **Doc fixes and others:** + - [#2621](https://github.com/validatorjs/validator.js/pull/2621) @mbtools + # 13.15.20 ### Fixes, New Locales and Enhancements diff --git a/package.json b/package.json index 106694955..aa019c56b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "validator", "description": "String validation and sanitization", - "version": "13.15.20", + "version": "13.15.22", "sideEffects": false, "homepage": "https://github.com/validatorjs/validator.js", "files": [ diff --git a/src/index.js b/src/index.js index b69c43649..839fe21ea 100644 --- a/src/index.js +++ b/src/index.js @@ -130,7 +130,7 @@ import isStrongPassword from './lib/isStrongPassword'; import isVAT from './lib/isVAT'; -const version = '13.15.20'; +const version = '13.15.22'; const validator = { version,