From d0cea00ff4ce75f4419eb0fd34f129512c26e814 Mon Sep 17 00:00:00 2001 From: Sebastian Seedorf Date: Wed, 27 May 2020 01:51:47 +0200 Subject: [PATCH] Added URL validator --- validators/string.test.ts | 14 +++++- validators/string.ts | 95 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/validators/string.test.ts b/validators/string.test.ts index 180c70d..070ad5b 100644 --- a/validators/string.test.ts +++ b/validators/string.test.ts @@ -38,17 +38,27 @@ Deno.test("isString (no match)", async () => { Deno.test("isURL (match)", async () => { const values = [ "http://google.com", + "http://10.1.1.1", + "http://10.1.1.254", + "http://223.255.255.254", + " data:,Hello World!" ]; for (const value of values) { - assertEquals(await validate(value, isURL()), []); + assertEquals(await validate(value, isURL({allowLocal: true, allowDataUrl: true})), [], value); } }); Deno.test("isURL (no match)", async () => { const values = [ "invalid", + "http://0.0.0.0", + "http://10.1.1.0", + "http://10.1.1.255", + "http://224.1.1.1", + "http://1.1.1.1.1", + " data:,Hello World!" ]; for (const value of values) { - assertNotEquals(await validate(value, isURL()), []); + assertNotEquals(await validate(value, isURL({allowLocal: true})), [], value); } }); diff --git a/validators/string.ts b/validators/string.ts index 39a010f..0e5aa1a 100644 --- a/validators/string.ts +++ b/validators/string.ts @@ -15,15 +15,104 @@ export function isString(): Validator { }; } -export function isURL(): Validator { +/** + * Thanks to https://gist.github.com/dperini/729294 + * Data URL https://gist.github.com/bgrins/6194623 + * + * @param param Options + */ +export function isURL( + { + protocols = ["http", "https"], + allowDataUrl = false, + allowUrl = true, + allowLocal = false, + allowIp = true, + allowDomain = true, + allowBasicAuth = false, + allowPort = true, + allowRecourcePath = true + }: { + protocols?: string[] | null; + allowDataUrl?: boolean; + allowUrl?: boolean; + allowLocal?: boolean; + allowIp?: boolean; + allowDomain?: boolean; + allowBasicAuth?: boolean; + allowPort?: boolean; + allowRecourcePath?: boolean; + } = {}, +): Validator { return { type: "isURL", extends: [isString()], check: (value: any) => { if (value === null || value === undefined) return; - if (value !== "http://google.com") { - return {}; + if (allowUrl) { + + let regex = "^"; + // protocol identifier (optional) + // short syntax // still required + if (protocols) { + regex += `(?:(?:(?:${protocols.join("|")}):)?\\/\\/)` + } else { + regex += `(?:(?:(?:[a-z]+):)?\/\/)` + } + // user:pass BasicAuth (optional) + if (allowBasicAuth) { + regex += "(?:\\S+(?::\\S*)?@)?"; + } + regex += "(?:"; // [hostname] start + if (allowIp && !allowLocal) { + regex += "(?!(?:10|127)(?:\\.\\d{1,3}){3})" + + "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" + + "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})"; + } + if (allowIp) { + regex += "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + + "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + + "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"; + } + if (allowIp || allowDomain) { + regex += "|"; // [hostname] ip end / domain start + } + if (allowDomain) { + // host & domain names, may end with dot + // can be replaced by a shortest alternative + // (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+ + regex += + "(?:" + + "(?:" + + "[a-z0-9\\u00a1-\\uffff]" + + "[a-z0-9\\u00a1-\\uffff_-]{0,62}" + + ")?" + + "[a-z0-9\\u00a1-\\uffff]\\." + + ")+" + + // TLD identifier name, may end with dot + "(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" + } + regex += ")"; // [hostname] end + if (allowPort) { + // port number (optional) + regex += "(?::\\d{2,5})?" + } + if (allowRecourcePath) { + // resource path (optional) + regex += "(?:[/?#]\\S*)?" + } + regex += "$"; + if (value.match(new RegExp(regex, "i"))) { + return; + } } + if (allowDataUrl) { + const regex = /^\s*data:([a-z]+\/[a-z]+(;[a-z\-]+\=[a-z\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i; + if (value.match(regex)) { + return; + } + } + return { }; }, message: (value: any, args?: Args) => { return `This value is not a valid URL.`;