From 65e018be7450e9dfe96d09e7abc844ad591ac22f Mon Sep 17 00:00:00 2001 From: Russ <8377044+rdubrock@users.noreply.github.com> Date: Thu, 24 Dec 2020 11:15:50 -0900 Subject: [PATCH] fix: allow ipv6 hostnames for http checks --- src/validation.test.ts | 25 ++++++++++++++----------- src/validation.ts | 30 ++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/validation.test.ts b/src/validation.test.ts index bd046992b..ea1b6761a 100644 --- a/src/validation.test.ts +++ b/src/validation.test.ts @@ -101,17 +101,6 @@ describe('bad targets', () => { expect(CheckValidation.target(CheckType.HTTP, 'https://suraj/dev')).toEqual('Target must have a valid hostname'); }); - it('should reject http targets with ipv6 domains', () => { - [ - 'https://[2001:0db8:1001:1001:1001:1001:1001:1001]/', - 'https://[2001:0db8:1001:1001:1001:1001:1001:1001]:8080/', - 'http://[2001:0db8:1001:1001:1001:1001:1001:1001]/', - 'http://[2001:0db8:1001:1001:1001:1001:1001:1001]:8080/', - ].forEach(() => - expect(CheckValidation.target(CheckType.HTTP, 'https://hostname/')).toEqual('Target must have a valid hostname') - ); - }); - it('should reject URLs without schema', () => { const testcases: string[] = ['example.org']; testcases.forEach((testcase: string) => { @@ -127,6 +116,11 @@ describe('bad targets', () => { }); }); + it('should reject malformed ipv6 https targets', () => { + const url = 'https://[2001:0db8:1001:1001:1001:1001:1001:1001/'; + expect(CheckValidation.target(CheckType.HTTP, url)).toBe('Target must be a valid web URL'); + }); + it('should reject ping targets with invalid hostnames', () => { const testcases: string[] = ['x.', '.y', 'x=y.org']; testcases.forEach((testcase: string) => { @@ -164,6 +158,15 @@ describe('good targets', () => { }); }); + it('should accept http targets with ipv6 domains', () => { + [ + 'https://[2001:0db8:1001:1001:1001:1001:1001:1001]/', + 'https://[2001:0db8:1001:1001:1001:1001:1001:1001]:8080/', + 'http://[2001:0db8:1001:1001:1001:1001:1001:1001]/', + 'http://[2001:0db8:1001:1001:1001:1001:1001:1001]:8080/', + ].forEach(url => expect(CheckValidation.target(CheckType.HTTP, url)).toBe(undefined)); + }); + it('should accept URL with IPv4 addresses as HTTP target', () => { const testcases: string[] = [ 'http://1.2.3.4/', diff --git a/src/validation.ts b/src/validation.ts index 2449cafc8..57cc3b869 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -210,17 +210,27 @@ function validateHttpTarget(target: string): string | undefined { if (!isValidUrl) { return 'Target must be a valid web URL'; } - const parsedUrl = new URL(target); - if (!parsedUrl.protocol) { - return 'Target must have a valid protocol'; - } + try { + const parsedUrl = new URL(target); + if (!parsedUrl.protocol) { + return 'Target must have a valid protocol'; + } + + // isWebUri will allow some invalid hostnames, so we need addional validation + const ipv6 = Address6.fromURL(target); + if (ipv6.address) { + return undefined; + } - // isWebUri will allow some invalid hostnames, so we need addional validation - const hostname = new URL(target).hostname; - if (validateHostname(hostname)) { - return 'Target must have a valid hostname'; + const hostname = parsedUrl.hostname; + if (validateHostname(hostname)) { + return 'Target must have a valid hostname'; + } + return undefined; + } catch (e) { + // The new URL constructor throws on invalid web URLs + return 'Target must be a valid web URL'; } - return undefined; } function validateHostname(target: string): string | undefined { @@ -229,7 +239,7 @@ function validateHostname(target: string): string | undefined { const pc = punycode.toASCII(target); // note that \w matches "_" const re = new RegExp(/^[a-z0-9]([-a-z0-9]{0,62}[a-z0-9])?(\.[a-z]([-a-z0-9]{0,62}[a-z0-9])?)+$/, 'i'); - if (!pc.match(re) && !ipv4.isValid() && !ipv6.isValid()) { + if (!pc.match(re) && !ipv4.valid && !ipv6.valid) { return 'Target must be a valid hostname'; }