├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── index.js ├── package.json └── test ├── is_http_uri.js ├── is_https_uri.js ├── is_uri.js └── is_web_uri.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | /*.log 3 | !/.coverignore 4 | !/.gitignore 5 | !/.jshintrc 6 | !/.travis.yml 7 | node_modules/ 8 | /*.exe 9 | /*.project 10 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node" : true, 3 | "undef": true, 4 | "unused": true, 5 | "indent": 4 6 | } 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # .npmignore file 2 | test/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "iojs" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Odysseas Tsatalos and oDesk Corporation 2 | 3 | This software is released under the MIT license: 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TAP=node_modules/.bin/tap 2 | LINT=node_modules/.bin/jshint 3 | 4 | test: lint 5 | $(TAP) test/*.js 6 | 7 | lint: 8 | $(LINT) index.js 9 | $(LINT) test/*.js 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | URI validation functions 2 | == 3 | [![Build Status](https://travis-ci.org/ogt/valid-url.png)](https://travis-ci.org/ogt/valid-url) 4 | 5 | ## Synopsis 6 | 7 | Common url validation methods 8 | ``` 9 | const validUrl = require('valid-url'); 10 | 11 | if (validUrl.isUri(url)){ 12 | console.log('Looks like an URI'); 13 | } else { 14 | console.log('Not a URI'); 15 | } 16 | ``` 17 | 18 | Replicates the functionality of Richard Sonnen perl module : 19 | http://search.cpan.org/~sonnen/Data-Validate-URI-0.01/lib/Data/Validate/URI.pm [full code here](http://anonscm.debian.org/gitweb/?p=users/dom/libdata-validate-uri-perl.git) 20 | into a nodejs module. Translated practically line by line from perl. 21 | It passes all the original tests. 22 | 23 | ## Description 24 | 25 | (copied from original perl module) 26 | 27 | > This module collects common URI validation routines to make input validation, and untainting easier and more readable. 28 | > All functions return an untainted value if the test passes, and undef if it fails. This means that you should always check for a defined status explicitly. Don't assume the return will be true. 29 | > The value to test is always the first (and often only) argument. 30 | > There are a number of other URI validation modules out there as well (see below.) This one focuses on being fast, lightweight, and relatively 'real-world'. i.e. it's good if you want to check user input, and don't need to parse out the URI/URL into chunks. 31 | > Right now the module focuses on HTTP URIs, since they're arguably the most common. If you have a specialized scheme you'd like to have supported, let me know. 32 | 33 | ## Installation 34 | 35 | ``` 36 | npm install valid-url 37 | ``` 38 | 39 | ## Methods 40 | ```javascript 41 | /* 42 | * @Function isUri(value) 43 | * 44 | * @Synopsis is the value a well-formed uri? 45 | * @Description 46 | Returns the untainted URI if the test value appears to be well-formed. Note that 47 | you may really want one of the more practical methods like is_http_uri or is_https_uri, 48 | since the URI standard (RFC 3986) allows a lot of things you probably don't want. 49 | * @Arguments 50 | * value The potential URI to test. 51 | * 52 | * @Returns The untainted RFC 3986 URI on success, undefined on failure. 53 | * @Notes 54 | This function does not make any attempt to check whether the URI is accessible 55 | or 'makes sense' in any meaningful way. It just checks that it is formatted 56 | correctly. 57 | * 58 | */ 59 | 60 | 61 | /* 62 | * @Function isHttpUri(value) 63 | * @Synopsis is the value a well-formed HTTP uri? 64 | * @Description 65 | Specialized version of isUri() that only likes http:// urls. As a result, it can 66 | also do a much more thorough job validating. Also, unlike isUri() it is more 67 | concerned with only allowing real-world URIs through. Things like relative 68 | hostnames are allowed by the standards, but probably aren't wise. Conversely, 69 | null paths aren't allowed per RFC 2616 (should be '/' instead), but are allowed 70 | by this function. 71 | 72 | This function only works for fully-qualified URIs. /bob.html won't work. 73 | See RFC 3986 for the appropriate method to turn a relative URI into an absolute 74 | one given its context. 75 | 76 | Returns the untainted URI if the test value appears to be well-formed. 77 | 78 | Note that you probably want to either call this in combo with is_https_uri(). i.e. 79 | 80 | if(isHttpUri(uri) || isHttpsUri(uri)) console.log('Good'); 81 | 82 | or use the convenience method isWebUri which is equivalent. 83 | 84 | * @Arguments 85 | * value The potential URI to test. 86 | * 87 | * @Returns The untainted RFC 3986 URI on success, undefined on failure. 88 | * @Notes 89 | This function does not make any attempt to check whether the URI is accessible 90 | or 'makes sense' in any meaningful way. It just checks that it is formatted 91 | correctly. 92 | */ 93 | 94 | 95 | 96 | /* 97 | * @Function isHttpsUri(value) 98 | * @Synopsis is the value a well-formed HTTPS uri? 99 | * @Description 100 | See is_http_uri() for details. This version only likes the https URI scheme. 101 | Otherwise it's identical to is_http_uri() 102 | * @Arguments 103 | * value The potential URI to test. 104 | * 105 | * @Returns The untainted RFC 3986 URI on success, undefined on failure. 106 | * @Notes 107 | This function does not make any attempt to check whether the URI is accessible 108 | or 'makes sense' in any meaningful way. It just checks that it is formatted 109 | correctly. 110 | */ 111 | 112 | 113 | /* 114 | * @Function isWebUri(value) 115 | * @Synopsis is the value a well-formed HTTP or HTTPS uri? 116 | * @Description 117 | This is just a convenience method that combines isHttpUri and isHttpsUri 118 | to accept most common real-world URLs. 119 | * @Arguments 120 | * value The potential URI to test. 121 | * 122 | * @Returns The untainted RFC 3986 URI on success, undefined on failure. 123 | * @Notes 124 | This function does not make any attempt to check whether the URI is accessible 125 | or 'makes sense' in any meaningful way. It just checks that it is formatted 126 | correctly. 127 | */ 128 | 129 | ``` 130 | 131 | ## See also 132 | 133 | RFC 3986, RFC 3966, RFC 4694, RFC 4759, RFC 4904 134 | 135 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 'use strict'; 3 | 4 | module.exports.is_uri = is_iri; 5 | module.exports.is_http_uri = is_http_iri; 6 | module.exports.is_https_uri = is_https_iri; 7 | module.exports.is_web_uri = is_web_iri; 8 | // Create aliases 9 | module.exports.isUri = is_iri; 10 | module.exports.isHttpUri = is_http_iri; 11 | module.exports.isHttpsUri = is_https_iri; 12 | module.exports.isWebUri = is_web_iri; 13 | 14 | 15 | // private function 16 | // internal URI spitter method - direct from RFC 3986 17 | var splitUri = function(uri) { 18 | var splitted = uri.match(/(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?/); 19 | return splitted; 20 | }; 21 | 22 | function is_iri(value) { 23 | if (!value) { 24 | return; 25 | } 26 | 27 | // check for illegal characters 28 | if (/[^a-z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\.\-\_\~\%]/i.test(value)) return; 29 | 30 | // check for hex escapes that aren't complete 31 | if (/%[^0-9a-f]/i.test(value)) return; 32 | if (/%[0-9a-f](:?[^0-9a-f]|$)/i.test(value)) return; 33 | 34 | var splitted = []; 35 | var scheme = ''; 36 | var authority = ''; 37 | var path = ''; 38 | var query = ''; 39 | var fragment = ''; 40 | var out = ''; 41 | 42 | // from RFC 3986 43 | splitted = splitUri(value); 44 | scheme = splitted[1]; 45 | authority = splitted[2]; 46 | path = splitted[3]; 47 | query = splitted[4]; 48 | fragment = splitted[5]; 49 | 50 | // scheme and path are required, though the path can be empty 51 | if (!(scheme && scheme.length && path.length >= 0)) return; 52 | 53 | // if authority is present, the path must be empty or begin with a / 54 | if (authority && authority.length) { 55 | if (!(path.length === 0 || /^\//.test(path))) return; 56 | } else { 57 | // if authority is not present, the path must not start with // 58 | if (/^\/\//.test(path)) return; 59 | } 60 | 61 | // scheme must begin with a letter, then consist of letters, digits, +, ., or - 62 | if (!/^[a-z][a-z0-9\+\-\.]*$/.test(scheme.toLowerCase())) return; 63 | 64 | // re-assemble the URL per section 5.3 in RFC 3986 65 | out += scheme + ':'; 66 | if (authority && authority.length) { 67 | out += '//' + authority; 68 | } 69 | 70 | out += path; 71 | 72 | if (query && query.length) { 73 | out += '?' + query; 74 | } 75 | 76 | if (fragment && fragment.length) { 77 | out += '#' + fragment; 78 | } 79 | 80 | return out; 81 | } 82 | 83 | function is_http_iri(value, allowHttps) { 84 | if (!is_iri(value)) { 85 | return; 86 | } 87 | 88 | var splitted = []; 89 | var scheme = ''; 90 | var authority = ''; 91 | var path = ''; 92 | var port = ''; 93 | var query = ''; 94 | var fragment = ''; 95 | var out = ''; 96 | 97 | // from RFC 3986 98 | splitted = splitUri(value); 99 | scheme = splitted[1]; 100 | authority = splitted[2]; 101 | path = splitted[3]; 102 | query = splitted[4]; 103 | fragment = splitted[5]; 104 | 105 | if (!scheme) return; 106 | 107 | if(allowHttps) { 108 | if (scheme.toLowerCase() != 'https') return; 109 | } else { 110 | if (scheme.toLowerCase() != 'http') return; 111 | } 112 | 113 | // fully-qualified URIs must have an authority section that is 114 | // a valid host 115 | if (!authority) { 116 | return; 117 | } 118 | 119 | // enable port component 120 | if (/:(\d+)$/.test(authority)) { 121 | port = authority.match(/:(\d+)$/)[0]; 122 | authority = authority.replace(/:\d+$/, ''); 123 | } 124 | 125 | out += scheme + ':'; 126 | out += '//' + authority; 127 | 128 | if (port) { 129 | out += port; 130 | } 131 | 132 | out += path; 133 | 134 | if(query && query.length){ 135 | out += '?' + query; 136 | } 137 | 138 | if(fragment && fragment.length){ 139 | out += '#' + fragment; 140 | } 141 | 142 | return out; 143 | } 144 | 145 | function is_https_iri(value) { 146 | return is_http_iri(value, true); 147 | } 148 | 149 | function is_web_iri(value) { 150 | return (is_http_iri(value) || is_https_iri(value)); 151 | } 152 | 153 | })(module); 154 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "valid-url", 3 | "description": "URI validation functions", 4 | "keywords": [ 5 | "url", 6 | "validation", 7 | "check", 8 | "checker", 9 | "pattern" 10 | ], 11 | "version": "1.0.5", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/ogt/valid-url.git" 15 | }, 16 | "homepage": "https://github.com/ogt/valid-url", 17 | "bugs": { 18 | "url": "https://github.com/ogt/valid-url" 19 | }, 20 | "license": "MIT", 21 | "main": "index.js", 22 | "scripts": { 23 | "test": "make test" 24 | }, 25 | "devDependencies": { 26 | "tap": "~0.4.3", 27 | "jshint": "~2.1.4" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/is_http_uri.js: -------------------------------------------------------------------------------- 1 | var test = require("tap").test, 2 | is_http_uri = require('../').is_http_uri; 3 | 4 | test("testing is_http_uri", function (t) { 5 | 6 | // valid 7 | t.ok(is_http_uri('http://www.richardsonnen.com/'), 'http://www.richardsonnen.com/'); 8 | t.ok(is_http_uri('http://www.richardsonnen.com'), 'http://www.richardsonnen.com'); 9 | t.ok(is_http_uri('http://www.richardsonnen.com/foo/bar/test.html'), 'http://www.richardsonnen.com/foo/bar/test.html'); 10 | t.ok(is_http_uri('http://www.richardsonnen.com/?foo=bar'), 'http://www.richardsonnen.com/?foo=bar'); 11 | t.ok(is_http_uri('http://www.richardsonnen.com:8080/test.html'), 'http://www.richardsonnen.com:8080/test.html'); 12 | t.ok(is_http_uri('http://example.w3.org/path%20with%20spaces.html'), 'http://example.w3.org/path%20with%20spaces.html'); 13 | t.ok(is_http_uri('http://192.168.0.1/'), 'http://192.168.0.1/'); 14 | 15 | // invalid 16 | t.notOk(is_http_uri(''), "bad: ''"); 17 | t.notOk(is_http_uri('ftp://ftp.richardsonnen.com'), "bad: 'ftp://ftp.richardsonnen.com'"); 18 | t.notOk(is_http_uri('http:www.richardsonnen.com'), "bad: 'http:www.richardsonnen.com'"); 19 | t.notOk(is_http_uri('https://www.richardsonnen.com'), "bad: 'https://www.richardsonnen.com'"); 20 | 21 | t.end(); 22 | }); 23 | -------------------------------------------------------------------------------- /test/is_https_uri.js: -------------------------------------------------------------------------------- 1 | var test = require("tap").test, 2 | is_https_uri = require('../').is_https_uri; 3 | 4 | test("testing is_https_uri", function (t) { 5 | 6 | // valid 7 | t.ok(is_https_uri('https://www.richardsonnen.com/'), 'https://www.richardsonnen.com/'); 8 | t.ok(is_https_uri('https://www.richardsonnen.com'), 'https://www.richardsonnen.com'); 9 | t.ok(is_https_uri('https://www.richardsonnen.com/foo/bar/test.html'), 'https://www.richardsonnen.com/foo/bar/test.html'); 10 | t.ok(is_https_uri('https://www.richardsonnen.com/?foo=bar'), 'https://www.richardsonnen.com/?foo=bar'); 11 | t.ok(is_https_uri('https://www.richardsonnen.com:8080/test.html'), 'https://www.richardsonnen.com:8080/test.html'); 12 | t.ok(is_https_uri('https://example.w3.org/path%20with%20spaces.html'), 'http://example.w3.org/path%20with%20spaces.html'); 13 | t.ok(is_https_uri('https://192.168.0.1/'), 'http://192.168.0.1/'); 14 | 15 | // invalid 16 | t.notOk(is_https_uri(''), "bad: ''"); 17 | t.notOk(is_https_uri('http://www.richardsonnen.com/'), 'http://www.richardsonnen.com/'); 18 | t.notOk(is_https_uri('ftp://ftp.richardsonnen.com'), "bad: 'ftp://ftp.richardsonnen.com'"); 19 | t.notOk(is_https_uri('https:www.richardsonnen.com'), "bad: 'https:www.richardsonnen.com'"); 20 | 21 | t.end(); 22 | }); 23 | -------------------------------------------------------------------------------- /test/is_uri.js: -------------------------------------------------------------------------------- 1 | var test = require("tap").test, 2 | is_uri = require('../').is_uri; 3 | 4 | test("testing is_uri", function (t) { 5 | 6 | // valid - from RFC 3986 for the most part 7 | t.ok(is_uri('http://localhost/'), 'http://localhost/'); 8 | t.ok(is_uri('http://example.w3.org/path%20with%20spaces.html'), 'http://example.w3.org/path%20with%20spaces.html'); 9 | t.ok(is_uri('http://example.w3.org/%20'), 'http://example.w3.org/%20'); 10 | t.ok(is_uri('ftp://ftp.is.co.za/rfc/rfc1808.txt'), 'ftp://ftp.is.co.za/rfc/rfc1808.txt'); 11 | t.ok(is_uri('ftp://ftp.is.co.za/../../../rfc/rfc1808.txt'), 'ftp://ftp.is.co.za/../../../rfc/rfc1808.txt'); 12 | t.ok(is_uri('http://www.ietf.org/rfc/rfc2396.txt'), 'http://www.ietf.org/rfc/rfc2396.txt'); 13 | t.ok(is_uri('ldap://[2001:db8::7]/c=GB?objectClass?one'), 'ldap://[2001:db8::7]/c=GB?objectClass?one'); 14 | t.ok(is_uri('mailto:John.Doe@example.com'), 'mailto:John.Doe@example.com'); 15 | t.ok(is_uri('news:comp.infosystems.www.servers.unix'), 'news:comp.infosystems.www.servers.unix'); 16 | t.ok(is_uri('tel:+1-816-555-1212'), 'tel:+1-816-555-1212'); 17 | t.ok(is_uri('telnet://192.0.2.16:80/'), 'telnet://192.0.2.16:80/'); 18 | t.ok(is_uri('urn:oasis:names:specification:docbook:dtd:xml:4.1.2'), 'urn:oasis:names:specification:docbook:dtd:xml:4.1.2'); 19 | 20 | 21 | // invalid 22 | t.notOk(is_uri(''), "bad: ''"); 23 | t.notOk(is_uri('foo'), 'bad: foo'); 24 | t.notOk(is_uri('foo@bar'), 'bad: foo@bar'); 25 | t.notOk(is_uri('http://'), 'bad: http://'); // illegal characters 26 | t.notOk(is_uri('://bob/'), 'bad: ://bob/'); // empty schema 27 | t.notOk(is_uri('1http://bob'), 'bad: 1http://bob/'); // bad schema 28 | t.notOk(is_uri('1http:////foo.html'), 'bad: 1http://bob/'); // bad path 29 | t.notOk(is_uri('http://example.w3.org/%illegal.html'), 'http://example.w3.org/%illegal.html'); 30 | t.notOk(is_uri('http://example.w3.org/%a'), 'http://example.w3.org/%a'); // partial escape 31 | t.notOk(is_uri('http://example.w3.org/%a/foo'), 'http://example.w3.org/%a/foo'); // partial escape 32 | t.notOk(is_uri('http://example.w3.org/%at'), 'http://example.w3.org/%at'); // partial escape 33 | 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/is_web_uri.js: -------------------------------------------------------------------------------- 1 | var test = require("tap").test, 2 | is_web_uri = require('../').is_web_uri; 3 | 4 | test("testing is_web_uri", function (t) { 5 | 6 | // valid 7 | t.ok(is_web_uri('https://www.richardsonnen.com/'), 'https://www.richardsonnen.com/'); 8 | t.ok(is_web_uri('https://www.richardsonnen.com'), 'https://www.richardsonnen.com'); 9 | t.ok(is_web_uri('https://www.richardsonnen.com/foo/bar/test.html'), 'https://www.richardsonnen.com/foo/bar/test.html'); 10 | t.ok(is_web_uri('https://www.richardsonnen.com/?foo=bar'), 'https://www.richardsonnen.com/?foo=bar'); 11 | t.ok(is_web_uri('https://www.richardsonnen.com:8080/test.html'), 'https://www.richardsonnen.com:8080/test.html'); 12 | t.ok(is_web_uri('http://www.richardsonnen.com/'), 'http://www.richardsonnen.com/'); 13 | t.ok(is_web_uri('http://www.richardsonnen.com'), 'http://www.richardsonnen.com'); 14 | t.ok(is_web_uri('http://www.richardsonnen.com/foo/bar/test.html'), 'http://www.richardsonnen.com/foo/bar/test.html'); 15 | t.ok(is_web_uri('http://www.richardsonnen.com/?foo=bar'), 'http://www.richardsonnen.com/?foo=bar'); 16 | t.ok(is_web_uri('http://www.richardsonnen.com:8080/test.html'), 'http://www.richardsonnen.com:8080/test.html'); 17 | t.ok(is_web_uri('http://example.w3.org/path%20with%20spaces.html'), 'http://example.w3.org/path%20with%20spaces.html'); 18 | t.ok(is_web_uri('http://192.168.0.1/'), 'http://192.168.0.1/'); 19 | 20 | // invalid 21 | t.ok(!is_web_uri(''), "bad: ''"); 22 | t.ok(!is_web_uri('ftp://ftp.richardsonnen.com'), "bad: 'ftp://ftp.richardsonnen.com'"); 23 | t.ok(!is_web_uri('https:www.richardsonnen.com'), "bad: 'http:www.richardsonnen.com'"); 24 | t.ok(!is_web_uri('http:www.richardsonnen.com'), "bad: 'http:www.richardsonnen.com'"); 25 | 26 | 27 | t.end(); 28 | }); 29 | --------------------------------------------------------------------------------