main.js (5388B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 'use strict'; 20 21 // MODULES // 22 23 var isString = require( './../../is-string' ).isPrimitive; 24 25 26 // VARIABLES // 27 28 /** 29 * Matches parts of a URI according to RFC 3986. 30 * 31 * ```text 32 * <scheme name> : <hierarchical part > [ ? <query> ] [ # <fragment> ] 33 * ``` 34 * 35 * Regular expression: `/(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?[^#]*)?(?:#.*)?/` 36 * 37 * - `(?:([^:\/?#]+):)` 38 * 39 * - match the scheme, including the `:`, but only capture the scheme name 40 * 41 * - `?` 42 * 43 * - match the scheme zero or one times 44 * 45 * - `(?:\/\/([^\/?#]*))` 46 * 47 * - match the hierarchical part which is everything which is not a `/`, `#`, or `?`, but only capture whatever comes after the `//` 48 * 49 * - `?` 50 * 51 * - match the hierarchical part zero or one times 52 * 53 * - `([^?#]*)` 54 * 55 * - capture everything (the path) until meeting a `?` or `#` 56 * 57 * - `(?:\?[^#]*)` 58 * 59 * - match, but don't capture, a query 60 * 61 * - `?` 62 * 63 * - match the query zero or one times 64 * 65 * - `(?:#.*)` 66 * 67 * - match, but don't capture, a fragment 68 * 69 * - `?` 70 * 71 * - match the fragment zero or one times 72 * 73 * @private 74 * @constant 75 * @type {RegExp} 76 * @default /(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?[^#]*)?(?:#.*)?/ 77 */ 78 var RE_URI = /(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?[^#]*)?(?:#.*)?/; // eslint-disable-line no-useless-escape 79 80 // Illegal characters (anything which is not in between the square brackets): 81 var RE_ILLEGALS = /[^a-z0-9:\/?#\[\]@!$&'()*+,;=.\-_~%]/i; // eslint-disable-line no-useless-escape 82 83 // Incomplete HEX escapes: 84 var RE_HEX1 = /%[^0-9a-f]/i; 85 var RE_HEX2 = /%[0-9a-f](:?[^0-9a-f]|$)/i; 86 87 // If authority is not present, path must not begin with '//' 88 var RE_PATH = /^\/\//; 89 90 // Scheme must begin with a letter, then consist of letters, digits, '+', '.', or '-' => e.g., 'http', 'https', 'ftp' 91 var RE_SCHEME = /^[a-z][a-z0-9+\-.]*$/; 92 93 94 // MAIN // 95 96 /** 97 * Tests if a value is a URI. 98 * 99 * @param {*} value - value to test 100 * @returns {boolean} boolean indicating if a value is a URI 101 * 102 * @example 103 * var bool = isURI( 'http://google.com' ); 104 * // returns true 105 * 106 * @example 107 * var bool = isURI( 'http://localhost/' ); 108 * // returns true 109 * 110 * @example 111 * var bool = isURI( 'http://example.w3.org/path%20with%20spaces.html' ); 112 * // returns true 113 * 114 * @example 115 * var bool = isURI( 'http://example.w3.org/%20' ); 116 * // returns true 117 * 118 * @example 119 * var bool = isURI( 'ftp://ftp.is.co.za/rfc/rfc1808.txt' ); 120 * // returns true 121 * 122 * @example 123 * var bool = isURI( 'ftp://ftp.is.co.za/../../../rfc/rfc1808.txt' ); 124 * // returns true 125 * 126 * @example 127 * var bool = isURI( 'http://www.ietf.org/rfc/rfc2396.txt' ); 128 * // returns true 129 * 130 * @example 131 * var bool = isURI( 'ldap://[2001:db8::7]/c=GB?objectClass?one' ); 132 * // returns true 133 * 134 * @example 135 * var bool = isURI( 'mailto:John.Doe@example.com' ); 136 * // returns true 137 * 138 * @example 139 * var bool = isURI( 'news:comp.infosystems.www.servers.unix' ); 140 * // returns true 141 * 142 * @example 143 * var bool = isURI( 'tel:+1-816-555-1212' ); 144 * // returns true 145 * 146 * @example 147 * var bool = isURI( 'telnet://192.0.2.16:80/' ); 148 * // returns true 149 * 150 * @example 151 * var bool = isURI( 'urn:oasis:names:specification:docbook:dtd:xml:4.1.2' ); 152 * // returns true 153 * 154 * @example 155 * // No scheme: 156 * var bool = isURI( '' ); 157 * // returns false 158 * 159 * @example 160 * // No scheme: 161 * var bool = isURI( 'foo' ); 162 * // returns false 163 * 164 * @example 165 * // No scheme: 166 * var bool = isURI( 'foo@bar' ); 167 * // returns false 168 * 169 * @example 170 * // No scheme: 171 * var bool = isURI( '://foo/' ); 172 * // returns false 173 * 174 * @example 175 * // Illegal characters: 176 * var bool = isURI( 'http://<foo>' ); 177 * // returns false 178 * 179 * @example 180 * // Invalid path: 181 * var bool = isURI( 'http:////foo.html' ); 182 * // returns false 183 * 184 * @example 185 * // Incomplete hex escapes... 186 * var bool = isURI( 'http://example.w3.org/%a' ); 187 * // returns false 188 * 189 * @example 190 * var bool = isURI( 'http://example.w3.org/%a/foo' ); 191 * // returns false 192 * 193 * @example 194 * var bool = isURI( 'http://example.w3.org/%at' ); 195 * // returns false 196 */ 197 function isURI( value ) { 198 var authority; 199 var scheme; 200 var parts; 201 var path; 202 203 if ( !isString( value ) ) { 204 return false; 205 } 206 // Check for illegal characters: 207 if ( RE_ILLEGALS.test( value ) ) { 208 return false; 209 } 210 // Check for incomplete HEX escapes: 211 if ( 212 RE_HEX1.test( value ) || 213 RE_HEX2.test( value ) 214 ) { 215 return false; 216 } 217 // Split the string into various URI components: 218 parts = value.match( RE_URI ); 219 scheme = parts[ 1 ]; 220 authority = parts[ 2 ]; 221 path = parts[ 3 ]; 222 223 // Scheme is required and must be valid: 224 if ( 225 !scheme || 226 !scheme.length || 227 !RE_SCHEME.test( scheme.toLowerCase() ) 228 ) { 229 return false; 230 } 231 // If authority is not present, path must not begin with `//`: 232 if ( 233 !authority && 234 RE_PATH.test( path ) 235 ) { 236 return false; 237 } 238 return true; 239 } 240 241 242 // EXPORTS // 243 244 module.exports = isURI;