├── .nvmrc ├── .travis.yml ├── .gitignore ├── test ├── complex-url.js ├── scheme.spec.js ├── index.spec.js ├── fragment.spec.js ├── host.spec.js ├── path.spec.js ├── real-life-examples.spec.js ├── pattern.spec.js └── params.spec.js ├── .babelrc ├── .editorconfig ├── lib ├── utilities │ └── exists.js ├── index.js ├── fragment.js ├── path.js ├── scheme.js ├── host.js ├── url-part.js ├── pattern.js └── params.js ├── src ├── utilities │ └── exists.js ├── fragment.js ├── scheme.js ├── path.js ├── host.js ├── index.js ├── url-part.js ├── params.js └── pattern.js ├── webpack.config.js ├── types └── index.d.ts ├── karma.conf.js ├── docs ├── index.css ├── index.html ├── index.js └── url-match.js ├── LICENSE ├── package.json ├── CHANGELOG.md └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 4.1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | /node_modules/ 4 | /temp/ 5 | /.idea/ 6 | -------------------------------------------------------------------------------- /test/complex-url.js: -------------------------------------------------------------------------------- 1 | export default 'http://user:pass@aaa.bbb.ccc:8080/ddd/eee?fff=ggg&hhh=iii#jjj'; 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": [ 4 | "transform-object-assign", 5 | ["transform-es2015-classes", {"loose": true}] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /lib/utilities/exists.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | exports.default = function (val) { 8 | return typeof val !== 'undefined' && val !== null; 9 | }; -------------------------------------------------------------------------------- /src/utilities/exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks whether value is defined and not null. 3 | * @param val 4 | * @returns {boolean} 5 | * @ignore 6 | */ 7 | export default function (val) { 8 | return (typeof val !== 'undefined') && (val !== null); 9 | } 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: path.resolve(__dirname, 'src/index.js'), 6 | output: { 7 | filename: 'url-match.js', 8 | path: path.resolve(__dirname, 'docs'), 9 | library: 'UrlMatch', 10 | libraryTarget: 'var' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | loaders: ['babel-loader'] 17 | } 18 | ] 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/fragment.js: -------------------------------------------------------------------------------- 1 | import UrlPart from './url-part'; 2 | 3 | 4 | export default class extends UrlPart { 5 | 6 | get is_required () { 7 | return false; 8 | } 9 | 10 | get invalidate_rules () { 11 | return [ 12 | // must not contain hash 13 | /#/ 14 | ]; 15 | } 16 | 17 | get sanitize_replacements () { 18 | return [ 19 | {substring: /\*/g, replacement: '.*'}, 20 | {substring: /\?/g, replacement: '\\\?'}, 21 | {substring: /\//g, replacement: '\\\/'} 22 | ]; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | interface UrlMatchFragmentDebug { 2 | pattern: string, 3 | value: string, 4 | result: boolean 5 | } 6 | 7 | interface UrlMatchPatternDebug { 8 | scheme: UrlMatchFragmentDebug, 9 | host: UrlMatchFragmentDebug, 10 | path: UrlMatchFragmentDebug, 11 | params: UrlMatchFragmentDebug, 12 | fragment: UrlMatchFragmentDebug, 13 | } 14 | 15 | export default class UrlMatch { 16 | constructor(patterns?: string[]); 17 | add(patterns?: string[]): string[]; 18 | remove(patterns?: string[]): string[]; 19 | test(content: string): boolean; 20 | debug(content: string): Record; 21 | } 22 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const webpack_config = require('./webpack.config'); 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | basePath: '', 6 | frameworks: ['jasmine'], 7 | files: ['test/**/*.spec.js'], 8 | preprocessors: {'test/**/*.spec.js': ['webpack']}, 9 | webpack: { 10 | mode: 'development', 11 | module: webpack_config.module 12 | }, 13 | webpackMiddleware: {noInfo: true}, 14 | reporters: ['coverage', 'mocha'], 15 | mochaReporter: {output: 'minimal'}, 16 | coverageReporter: { 17 | type: 'html', 18 | dir: 'temp/coverage' 19 | }, 20 | browsers: ['ChromeHeadless'], 21 | singleRun: true 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /docs/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 3em; 3 | background: #fff; 4 | color: #333; 5 | font-family: sans-serif; 6 | line-height: 1.5em; 7 | } 8 | 9 | textarea { 10 | box-sizing: border-box; 11 | width: 100%; 12 | font-family: monospace; 13 | font-size: 1em; 14 | line-height: 1.5em; 15 | } 16 | 17 | table { 18 | width: 100%; 19 | border-collapse: collapse; 20 | } 21 | 22 | th, td { 23 | border: 1px solid #ccc; 24 | } 25 | 26 | .result-ok, .result-fail { 27 | padding: 0.25em; 28 | border: 1px solid #fff; 29 | } 30 | 31 | .result-ok { 32 | color: #0c0; 33 | background: #00cc0033; 34 | } 35 | 36 | .result-fail { 37 | color: #c00; 38 | background: #cc000033; 39 | } 40 | -------------------------------------------------------------------------------- /src/scheme.js: -------------------------------------------------------------------------------- 1 | import UrlPart from './url-part'; 2 | import exists from './utilities/exists'; 3 | 4 | 5 | export default class extends UrlPart { 6 | 7 | validate (pattern = this.original_pattern) { 8 | if (exists(pattern)) { 9 | const re = new RegExp( 10 | '^(' + 11 | '\\*' + // single wildcard 12 | '|' + // or 13 | '[a-z]+' + // any string of lowercase letters 14 | ')$' 15 | ); 16 | return re.test(pattern); 17 | } 18 | return false; 19 | } 20 | 21 | get sanitize_replacements () { 22 | return [ 23 | // when using wildcard, only match http(s) 24 | {substring: '*', replacement: 'https?'} 25 | ]; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/path.js: -------------------------------------------------------------------------------- 1 | import UrlPart from './url-part'; 2 | 3 | 4 | export default class extends UrlPart { 5 | 6 | get default_value () { 7 | return ''; 8 | } 9 | 10 | get sanitize_replacements () { 11 | return [ 12 | // escape brackets 13 | {substring: /\(/, replacement: '\\\('}, 14 | {substring: /\)/, replacement: '\\\)'}, 15 | // assume trailing slash at the end of path is optional 16 | {substring: /\/$/, replacement: '\\/?'}, 17 | {substring: /\/\*$/, replacement: '((\/?)|\/*)'}, 18 | // plus sign 19 | {substring: /\+/, replacement: '\\\+'}, 20 | // allow letters, numbers, plus signs, hyphens, dots, slashes 21 | // and underscores instead of wildcard 22 | {substring: /\*/g, replacement: '[a-zA-Z0-9\+-./_:~!$&\'\(\)\*,;=@%]*'} 23 | ]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/host.js: -------------------------------------------------------------------------------- 1 | import UrlPart from './url-part'; 2 | 3 | 4 | export default class extends UrlPart { 5 | 6 | get validate_rules () { 7 | return [ 8 | // should not be empty 9 | /.+/ 10 | ]; 11 | } 12 | 13 | get invalidate_rules () { 14 | return [ 15 | // two asterisks in a row 16 | /\*\*/, 17 | // asterisk not followed by a dot 18 | /\*[^\.]+/, 19 | // asterisk not at the beginning 20 | /.\*/, 21 | // starts with dot or hyphen 22 | /^(\.|-)/, 23 | // ends with dot or hyphen 24 | /(\.|-)$/, 25 | // anything except characters, numbers, hyphen, dot and asterisk 26 | /[^a-z0-9-.\*]/ 27 | ]; 28 | } 29 | 30 | get sanitize_replacements () { 31 | return [ 32 | // make asterisk and dot at the beginning optional 33 | {substring: /^\*\./, replacement: '(*.)?'}, 34 | // escape all dots 35 | {substring: '.', replacement: '\\.'}, 36 | // replace asterisks with pattern 37 | {substring: '*', replacement: '[a-z0-9-_.]+'} 38 | ]; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Riki Fridrich (https://github.com/fczbkk) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UrlMatch 6 | 7 | 8 | 9 | 10 | 11 | 12 |

UrlMatch

13 | 14 |

15 |
16 | 18 |
19 | 20 |

21 |
22 | 26 |
27 | 28 |

Results

29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
*://*.google.com/*http://*.facebook.com/*
http://google.com/http://google.com/
44 |
45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Pattern from './pattern'; 2 | 3 | 4 | export default class { 5 | 6 | constructor (patterns = []) { 7 | this.patterns = []; 8 | this.add(patterns); 9 | } 10 | 11 | add (patterns = []) { 12 | if (typeof patterns === 'string') { 13 | patterns = [patterns]; 14 | } 15 | 16 | patterns.forEach((pattern) => { 17 | if (this.patterns.indexOf(pattern) === -1) { 18 | this.patterns.push(pattern); 19 | } 20 | }); 21 | 22 | return this.patterns; 23 | } 24 | 25 | remove (patterns = []) { 26 | if (typeof patterns === 'string') { 27 | patterns = [patterns]; 28 | } 29 | 30 | this.patterns = this.patterns.filter((pattern) => { 31 | return patterns.indexOf(pattern) === -1; 32 | }); 33 | 34 | return this.patterns; 35 | } 36 | 37 | test (content) { 38 | let result = false; 39 | 40 | this.patterns.forEach((pattern) => { 41 | const pattern_obj = new Pattern(pattern); 42 | if (pattern_obj.test(content) === true) { 43 | result = true; 44 | } 45 | }); 46 | 47 | return result; 48 | } 49 | 50 | debug (content) { 51 | let result = {}; 52 | 53 | this.patterns.forEach((pattern) => { 54 | const pattern_obj = new Pattern(pattern); 55 | result[pattern] = pattern_obj.debug(content); 56 | }); 57 | 58 | return result; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/url-part.js: -------------------------------------------------------------------------------- 1 | import exists from './utilities/exists'; 2 | 3 | 4 | export default class { 5 | 6 | constructor (pattern) { 7 | this.is_strict = false; 8 | this.original_pattern = pattern; 9 | this.pattern = this.sanitize(pattern); 10 | } 11 | 12 | get default_value () { 13 | return null; 14 | } 15 | 16 | get is_required () { 17 | return true; 18 | } 19 | 20 | get validate_rules () { 21 | return []; 22 | } 23 | 24 | get invalidate_rules () { 25 | return []; 26 | } 27 | 28 | validate (pattern = this.original_pattern) { 29 | if (exists(pattern)) { 30 | let result = true; 31 | 32 | this.validate_rules.forEach((rule) => { 33 | if (!rule.test(pattern)) { 34 | result = false; 35 | } 36 | }); 37 | 38 | this.invalidate_rules.forEach((rule) => { 39 | if (rule.test(pattern)) { 40 | result = false; 41 | } 42 | }); 43 | 44 | return result; 45 | } 46 | 47 | return !this.is_required; 48 | } 49 | 50 | test (content = '', pattern = this.pattern) { 51 | if (content === null) { 52 | content = ''; 53 | } 54 | 55 | if (exists(pattern)) { 56 | return pattern.test(content); 57 | } 58 | 59 | return true; 60 | } 61 | 62 | get sanitize_replacements () { 63 | return []; 64 | } 65 | 66 | sanitize (pattern = this.original_pattern) { 67 | if (!exists(pattern)) { 68 | pattern = this.default_value; 69 | } 70 | 71 | if (exists(pattern) && this.validate(pattern)) { 72 | this.sanitize_replacements.forEach(({substring, replacement}) => { 73 | pattern = pattern.replace(substring, replacement); 74 | }); 75 | return new RegExp('^' + pattern + '$'); 76 | } 77 | return null; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fczbkk/url-match", 3 | "title": "UrlMatch", 4 | "version": "3.5.0", 5 | "description": "JavaScript object for matching URLs against patterns.", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+ssh://git@github.com/fczbkk/UrlMatch.git" 9 | }, 10 | "author": { 11 | "name": "Riki Fridrich", 12 | "email": "riki@fczbkk.com", 13 | "url": "http://fczbkk.com/" 14 | }, 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/fczbkk/UrlMatch/issues" 18 | }, 19 | "homepage": "https://github.com/fczbkk/UrlMatch#readme", 20 | "scripts": { 21 | "cleanup": "rimraf temp lib", 22 | "prebuild": "npm run test && npm run cleanup", 23 | "build": "babel src -d lib", 24 | "docs": "webpack --config ./webpack.config.js", 25 | "test": "karma start ./karma.conf.js", 26 | "dev": "npm run test -- --no-single-run --auto-watch", 27 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", 28 | "postversion": "git push && git push --tags && npm publish --access=public", 29 | "version": "npm run build && npm run docs && npm run changelog && git add -A", 30 | "doc": "documentation readme ./src/index.js -s 'Documentation'" 31 | }, 32 | "main": "lib/index.js", 33 | "devDependencies": { 34 | "babel-cli": "^6.26.0", 35 | "babel-loader": "^7.1.4", 36 | "babel-plugin-transform-es2015-classes": "^6.24.1", 37 | "babel-plugin-transform-object-assign": "^6.22.0", 38 | "babel-preset-es2015": "^6.24.1", 39 | "conventional-changelog-cli": "^1.3.22", 40 | "documentation": "^6.3.2", 41 | "jasmine-core": "^3.1.0", 42 | "karma": "^2.0.2", 43 | "karma-chrome-launcher": "^2.2.0", 44 | "karma-coverage": "^1.1.1", 45 | "karma-jasmine": "^1.1.1", 46 | "karma-mocha-reporter": "^2.2.5", 47 | "karma-webpack": "^3.0.0", 48 | "mocha": "^5.1.1", 49 | "rimraf": "^2.6.2", 50 | "webpack": "^4.6.0", 51 | "webpack-cli": "^2.0.15" 52 | }, 53 | "dependencies": { 54 | "array-reduce-prototypejs-fix": "^1.2.0" 55 | }, 56 | "types": "types/index.d.ts" 57 | } 58 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _pattern = require('./pattern'); 8 | 9 | var _pattern2 = _interopRequireDefault(_pattern); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 14 | 15 | var _class = function () { 16 | function _class() { 17 | var patterns = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 18 | 19 | _classCallCheck(this, _class); 20 | 21 | this.patterns = []; 22 | this.add(patterns); 23 | } 24 | 25 | _class.prototype.add = function add() { 26 | var _this = this; 27 | 28 | var patterns = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 29 | 30 | if (typeof patterns === 'string') { 31 | patterns = [patterns]; 32 | } 33 | 34 | patterns.forEach(function (pattern) { 35 | if (_this.patterns.indexOf(pattern) === -1) { 36 | _this.patterns.push(pattern); 37 | } 38 | }); 39 | 40 | return this.patterns; 41 | }; 42 | 43 | _class.prototype.remove = function remove() { 44 | var patterns = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 45 | 46 | if (typeof patterns === 'string') { 47 | patterns = [patterns]; 48 | } 49 | 50 | this.patterns = this.patterns.filter(function (pattern) { 51 | return patterns.indexOf(pattern) === -1; 52 | }); 53 | 54 | return this.patterns; 55 | }; 56 | 57 | _class.prototype.test = function test(content) { 58 | var result = false; 59 | 60 | this.patterns.forEach(function (pattern) { 61 | var pattern_obj = new _pattern2.default(pattern); 62 | if (pattern_obj.test(content) === true) { 63 | result = true; 64 | } 65 | }); 66 | 67 | return result; 68 | }; 69 | 70 | _class.prototype.debug = function debug(content) { 71 | var result = {}; 72 | 73 | this.patterns.forEach(function (pattern) { 74 | var pattern_obj = new _pattern2.default(pattern); 75 | result[pattern] = pattern_obj.debug(content); 76 | }); 77 | 78 | return result; 79 | }; 80 | 81 | return _class; 82 | }(); 83 | 84 | exports.default = _class; -------------------------------------------------------------------------------- /lib/fragment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _urlPart = require('./url-part'); 10 | 11 | var _urlPart2 = _interopRequireDefault(_urlPart); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 18 | 19 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 20 | 21 | var _class = function (_UrlPart) { 22 | _inherits(_class, _UrlPart); 23 | 24 | function _class() { 25 | _classCallCheck(this, _class); 26 | 27 | return _possibleConstructorReturn(this, _UrlPart.apply(this, arguments)); 28 | } 29 | 30 | _createClass(_class, [{ 31 | key: 'is_required', 32 | get: function get() { 33 | return false; 34 | } 35 | }, { 36 | key: 'invalidate_rules', 37 | get: function get() { 38 | return [ 39 | // must not contain hash 40 | /#/]; 41 | } 42 | }, { 43 | key: 'sanitize_replacements', 44 | get: function get() { 45 | return [{ substring: /\*/g, replacement: '.*' }, { substring: /\?/g, replacement: '\\\?' }, { substring: /\//g, replacement: '\\\/' }]; 46 | } 47 | }]); 48 | 49 | return _class; 50 | }(_urlPart2.default); 51 | 52 | exports.default = _class; -------------------------------------------------------------------------------- /docs/index.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function () { 2 | var patterns_field = document.getElementById('patterns'); 3 | var urls_field = document.getElementById('urls'); 4 | var results_field = document.getElementById('results') 5 | 6 | patterns_field.addEventListener('change', displayResults); 7 | patterns_field.addEventListener('keyup', displayResults); 8 | urls_field.addEventListener('change', displayResults); 9 | urls_field.addEventListener('keyup', displayResults); 10 | 11 | function getInput (field) { 12 | return splitMultiline(field.value); 13 | } 14 | 15 | function splitMultiline (input) { 16 | return input.split('\n'); 17 | } 18 | 19 | function getWrappedDetail (data) { 20 | var class_name = (data.result) ? 'result-ok' : 'result-fail'; 21 | return ( 22 | '' 23 | + (data.value === '' ? ' ' : data.value) 24 | + '' 25 | ); 26 | } 27 | 28 | function getResultDetail (data) { 29 | var parts = []; 30 | 31 | parts.push(getWrappedDetail(data.scheme)); 32 | parts.push('://'); 33 | parts.push(getWrappedDetail(data.host)); 34 | parts.push('/'); 35 | parts.push(getWrappedDetail(data.path)); 36 | 37 | if (data.params.value !== null) { 38 | parts.push('?'); 39 | parts.push(getWrappedDetail(data.params)); 40 | } 41 | 42 | if (data.fragment.value !== null) { 43 | parts.push('#'); 44 | parts.push(getWrappedDetail(data.fragment)); 45 | } 46 | 47 | return parts.join(''); 48 | } 49 | 50 | function getPatternHeader (pattern) { 51 | return '' + pattern + ''; 52 | } 53 | 54 | function getNonEmpty (input) { 55 | return input !== ''; 56 | } 57 | 58 | function displayResults () { 59 | var patterns = getInput(patterns_field).filter(getNonEmpty); 60 | var urls = getInput(urls_field).filter(getNonEmpty); 61 | var url_match = new UrlMatch.default(patterns); 62 | 63 | var table_head = patterns.map(getPatternHeader).join(''); 64 | 65 | var table_body = urls.map( 66 | function (url) { 67 | var data = url_match.debug(url); 68 | return '' + patterns.map( 69 | function (pattern) { 70 | return '' + getResultDetail(data[pattern]) + ''; 71 | } 72 | ).join('') + ''; 73 | } 74 | ).join(''); 75 | 76 | var table = '' + 77 | '' + table_head + '' + 78 | '' + table_body + '' + 79 | '
'; 80 | 81 | results_field.innerHTML = table; 82 | } 83 | 84 | displayResults(); 85 | }); 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /lib/path.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _urlPart = require('./url-part'); 10 | 11 | var _urlPart2 = _interopRequireDefault(_urlPart); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 18 | 19 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 20 | 21 | var _class = function (_UrlPart) { 22 | _inherits(_class, _UrlPart); 23 | 24 | function _class() { 25 | _classCallCheck(this, _class); 26 | 27 | return _possibleConstructorReturn(this, _UrlPart.apply(this, arguments)); 28 | } 29 | 30 | _createClass(_class, [{ 31 | key: 'default_value', 32 | get: function get() { 33 | return ''; 34 | } 35 | }, { 36 | key: 'sanitize_replacements', 37 | get: function get() { 38 | return [ 39 | // escape brackets 40 | { substring: /\(/, replacement: '\\\(' }, { substring: /\)/, replacement: '\\\)' }, 41 | // assume trailing slash at the end of path is optional 42 | { substring: /\/$/, replacement: '\\/?' }, { substring: /\/\*$/, replacement: '((\/?)|\/*)' }, 43 | // plus sign 44 | { substring: /\+/, replacement: '\\\+' }, 45 | // allow letters, numbers, plus signs, hyphens, dots, slashes 46 | // and underscores instead of wildcard 47 | { substring: /\*/g, replacement: '[a-zA-Z0-9\+-./_:~!$&\'\(\)\*,;=@%]*' }]; 48 | } 49 | }]); 50 | 51 | return _class; 52 | }(_urlPart2.default); 53 | 54 | exports.default = _class; -------------------------------------------------------------------------------- /lib/scheme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _urlPart = require('./url-part'); 10 | 11 | var _urlPart2 = _interopRequireDefault(_urlPart); 12 | 13 | var _exists = require('./utilities/exists'); 14 | 15 | var _exists2 = _interopRequireDefault(_exists); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 22 | 23 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 24 | 25 | var _class = function (_UrlPart) { 26 | _inherits(_class, _UrlPart); 27 | 28 | function _class() { 29 | _classCallCheck(this, _class); 30 | 31 | return _possibleConstructorReturn(this, _UrlPart.apply(this, arguments)); 32 | } 33 | 34 | _class.prototype.validate = function validate() { 35 | var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.original_pattern; 36 | 37 | if ((0, _exists2.default)(pattern)) { 38 | var re = new RegExp('^(' + '\\*' + // single wildcard 39 | '|' + // or 40 | '[a-z]+' + // any string of lowercase letters 41 | ')$'); 42 | return re.test(pattern); 43 | } 44 | return false; 45 | }; 46 | 47 | _createClass(_class, [{ 48 | key: 'sanitize_replacements', 49 | get: function get() { 50 | return [ 51 | // when using wildcard, only match http(s) 52 | { substring: '*', replacement: 'https?' }]; 53 | } 54 | }]); 55 | 56 | return _class; 57 | }(_urlPart2.default); 58 | 59 | exports.default = _class; -------------------------------------------------------------------------------- /src/params.js: -------------------------------------------------------------------------------- 1 | import UrlPart from './url-part'; 2 | import exists from './utilities/exists'; 3 | import arrayReduce from 'array-reduce-prototypejs-fix'; 4 | 5 | 6 | export default class extends UrlPart { 7 | 8 | get is_required () { 9 | return false; 10 | } 11 | 12 | get invalidate_rules () { 13 | return [ 14 | // two equal signs in a row 15 | /==/, 16 | // equal signs undivided by ampersand 17 | /=[^&]+=/, 18 | // single equal sign 19 | /^=$/ 20 | ]; 21 | } 22 | 23 | sanitize (pattern = this.original_pattern) { 24 | // strict mode 25 | if (typeof pattern === 'string' && pattern.substring(0, 1) === '!') { 26 | pattern = pattern.substring(1); 27 | this.is_strict = true; 28 | } 29 | 30 | if (pattern === '*' || pattern === '') { 31 | pattern = null 32 | } 33 | 34 | const result = []; 35 | 36 | if (exists(pattern)) { 37 | 38 | // replace asterisks 39 | pattern.split('&').forEach((pair) => { 40 | let [key, val] = pair.split('='); 41 | 42 | // if key is asterisk, then at least one character is required 43 | key = (key === '*') ? '.+' : key.replace(/\*/g, '.*'); 44 | 45 | if (!exists(val) || val === '') { 46 | // if value is missing, it is prohibited 47 | // only equal sign is allowed 48 | val = '=?'; 49 | } else { 50 | // if value match is universal, the value is optional 51 | // thus the equal sign is optional 52 | val = (val === '*') ? '=?.*' : '=' + val.replace(/\*/g, '.*'); 53 | } 54 | 55 | // escape all brackets 56 | val = val.replace(/[\[\](){}]/g, '\\$&'); 57 | 58 | result.push(key + val); 59 | }); 60 | 61 | } 62 | 63 | return result; 64 | } 65 | 66 | test (content = '', patterns = this.pattern) { 67 | let result = true; 68 | 69 | if (exists(patterns)) { 70 | 71 | // special case, when we want to strictly match no params, e.g. '*://*/*?!' 72 | if (this.is_strict && (content === null) && (patterns.length === 0)) { 73 | return true; 74 | } 75 | 76 | result = arrayReduce(patterns, (previous_result, pattern) => { 77 | const re = new RegExp('(^|\&)' + pattern + '(\&|$)'); 78 | return previous_result && re.test(content); 79 | }, result); 80 | 81 | if (this.is_strict === true) { 82 | if (typeof content === 'string') { 83 | const wrapped_patterns = patterns 84 | .map((pattern) => `(${pattern})`) 85 | .join('|'); 86 | const re = new RegExp('(^|\&)(' + wrapped_patterns + ')(\&|$)'); 87 | 88 | result = arrayReduce(content.split('&'), (previous_result, pair) => { 89 | return previous_result && re.test(pair); 90 | }, result); 91 | } else { 92 | result = false; 93 | } 94 | } 95 | 96 | } 97 | 98 | return result; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /lib/host.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _urlPart = require('./url-part'); 10 | 11 | var _urlPart2 = _interopRequireDefault(_urlPart); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 18 | 19 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 20 | 21 | var _class = function (_UrlPart) { 22 | _inherits(_class, _UrlPart); 23 | 24 | function _class() { 25 | _classCallCheck(this, _class); 26 | 27 | return _possibleConstructorReturn(this, _UrlPart.apply(this, arguments)); 28 | } 29 | 30 | _createClass(_class, [{ 31 | key: 'validate_rules', 32 | get: function get() { 33 | return [ 34 | // should not be empty 35 | /.+/]; 36 | } 37 | }, { 38 | key: 'invalidate_rules', 39 | get: function get() { 40 | return [ 41 | // two asterisks in a row 42 | /\*\*/, 43 | // asterisk not followed by a dot 44 | /\*[^\.]+/, 45 | // asterisk not at the beginning 46 | /.\*/, 47 | // starts with dot or hyphen 48 | /^(\.|-)/, 49 | // ends with dot or hyphen 50 | /(\.|-)$/, 51 | // anything except characters, numbers, hyphen, dot and asterisk 52 | /[^a-z0-9-.\*]/]; 53 | } 54 | }, { 55 | key: 'sanitize_replacements', 56 | get: function get() { 57 | return [ 58 | // make asterisk and dot at the beginning optional 59 | { substring: /^\*\./, replacement: '(*.)?' }, 60 | // escape all dots 61 | { substring: '.', replacement: '\\.' }, 62 | // replace asterisks with pattern 63 | { substring: '*', replacement: '[a-z0-9-_.]+' }]; 64 | } 65 | }]); 66 | 67 | return _class; 68 | }(_urlPart2.default); 69 | 70 | exports.default = _class; -------------------------------------------------------------------------------- /test/scheme.spec.js: -------------------------------------------------------------------------------- 1 | import Scheme from './../src/scheme'; 2 | 3 | 4 | describe('Scheme', function() { 5 | 6 | let scheme; 7 | 8 | beforeEach(function() { 9 | scheme = new Scheme(); 10 | }); 11 | 12 | describe('validation', function() { 13 | 14 | it('should not validate if empty', function() { 15 | expect(scheme.validate()).toBe(false); 16 | expect(scheme.validate('')).toBe(false); 17 | }); 18 | 19 | it('should validate asterisk', function() { 20 | expect(scheme.validate('*')).toBe(true); 21 | }); 22 | 23 | it('should not validate more than one asterisk', function() { 24 | expect(scheme.validate('**')).toBe(false); 25 | expect(scheme.validate('*a*')).toBe(false); 26 | expect(scheme.validate('**a')).toBe(false); 27 | expect(scheme.validate('a**')).toBe(false); 28 | expect(scheme.validate('a**b')).toBe(false); 29 | expect(scheme.validate('*a*b')).toBe(false); 30 | expect(scheme.validate('a*b*')).toBe(false); 31 | expect(scheme.validate('a*b*c')).toBe(false); 32 | }); 33 | 34 | it('should validate string of characters', function() { 35 | expect(scheme.validate('aaa')).toBe(true); 36 | expect(scheme.validate('http')).toBe(true); 37 | expect(scheme.validate('https')).toBe(true); 38 | }); 39 | 40 | it('should not validate numbers', function() { 41 | expect(scheme.validate('123')).toBe(false); 42 | expect(scheme.validate('aaa123')).toBe(false); 43 | expect(scheme.validate('123bbb')).toBe(false); 44 | expect(scheme.validate('aaa123bbb')).toBe(false); 45 | }); 46 | 47 | it('should not validate special characters', function() { 48 | expect(scheme.validate('-')).toBe(false); 49 | expect(scheme.validate('?')).toBe(false); 50 | expect(scheme.validate('!')).toBe(false); 51 | expect(scheme.validate('aaa-')).toBe(false); 52 | expect(scheme.validate('-aaa')).toBe(false); 53 | expect(scheme.validate('aaa-bbb')).toBe(false); 54 | }); 55 | 56 | }); 57 | 58 | describe('sanitize', function() { 59 | 60 | it('should sanitize *', function() { 61 | expect(scheme.sanitize('*')).toEqual(/^https?$/); 62 | }); 63 | 64 | it('should sanitize custom protocol', function() { 65 | expect(scheme.sanitize('http')).toEqual(/^http$/); 66 | expect(scheme.sanitize('https')).toEqual(/^https$/); 67 | }); 68 | 69 | }); 70 | 71 | describe('test', function() { 72 | 73 | it('should match only specific scheme when provided', function() { 74 | expect(scheme.test('aaa', scheme.sanitize('aaaaaa'))).toBe(false); 75 | expect(scheme.test('aaa', scheme.sanitize('aaabbb'))).toBe(false); 76 | expect(scheme.test('aaa', scheme.sanitize('bbbaaa'))).toBe(false); 77 | expect(scheme.test('aaa', scheme.sanitize('bbb'))).toBe(false); 78 | }); 79 | 80 | return it('should only match `http` or `https` on universal pattern', function() { 81 | const pattern = scheme.sanitize('*'); 82 | expect(scheme.test('http', pattern)).toBe(true); 83 | expect(scheme.test('https', pattern)).toBe(true); 84 | expect(scheme.test('aaa', pattern)).toBe(false); 85 | }); 86 | 87 | }); 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /src/pattern.js: -------------------------------------------------------------------------------- 1 | import Scheme from './scheme'; 2 | import Host from './host'; 3 | import Path from './path'; 4 | import Params from './params'; 5 | import Fragment from './fragment'; 6 | import exists from './utilities/exists'; 7 | 8 | 9 | const split_re = new RegExp( 10 | '^' + // beginning 11 | '([a-z]+|\\*)*' + // (1) scheme 12 | '://' + // scheme separator 13 | '([^\\/\\#\\?]+@)*' + // (2) username and/or password 14 | '([\\w\\*\\.\\-]+)*' + // (3) host 15 | '(\\:\\d+)*' + // (4) port number 16 | '(/([^\\?\\#]*))*' + // (5) path, (6) excluding slash 17 | '(\\?([^\\#]*))*' + // (7) params, (8) excluding question mark 18 | '(\\#(.*))*' // (9) fragment, (10) excluding hash 19 | ); 20 | 21 | 22 | const parts_map = { 23 | scheme: 1, 24 | host: 3, 25 | path: 6, 26 | params: 8, 27 | fragment: 10 28 | }; 29 | 30 | 31 | export default class { 32 | 33 | constructor (pattern) { 34 | if (pattern === '*' || pattern === '') { 35 | pattern = '*://*/*?*#*'; 36 | } 37 | 38 | this.original_pattern = pattern; 39 | this.pattern = this.sanitize(pattern); 40 | this.url_parts = this.getUrlParts(this.pattern); 41 | } 42 | 43 | split (pattern = '', empty_value = null) { 44 | const result = {}; 45 | const parts = pattern.match(split_re); 46 | 47 | for (const key in parts_map) { 48 | const val = parts_map[key]; 49 | result[key] = (exists(parts) && exists(parts[val])) 50 | ? parts[val] 51 | : empty_value; 52 | } 53 | 54 | return result; 55 | } 56 | 57 | getUrlParts (pattern = this.pattern) { 58 | const splits = this.split(pattern); 59 | return { 60 | scheme: new Scheme(splits.scheme), 61 | host: new Host(splits.host), 62 | path: new Path(splits.path), 63 | params: new Params(splits.params), 64 | fragment: new Fragment(splits.fragment) 65 | } 66 | } 67 | 68 | sanitize (pattern = this.original_pattern) { 69 | const universal_pattern = '*://*/*?*#*'; 70 | if (pattern === '*' || pattern === '') { 71 | pattern = universal_pattern; 72 | } 73 | return pattern; 74 | } 75 | 76 | validate (url_parts = this.url_parts) { 77 | let result = true; 78 | 79 | for (const key in url_parts) { 80 | const val = url_parts[key]; 81 | if (!val.validate()) { 82 | result = false; 83 | } 84 | } 85 | 86 | return result; 87 | } 88 | 89 | test (url) { 90 | let result = false; 91 | 92 | if (exists(url)) { 93 | result = true; 94 | const splits = this.split(url); 95 | ['scheme', 'host', 'path', 'params', 'fragment'].forEach((part) => { 96 | if (!this.url_parts[part].test(splits[part])) { 97 | result = false; 98 | } 99 | }); 100 | } 101 | 102 | return result; 103 | } 104 | 105 | debug (url) { 106 | const splits = this.split(url); 107 | const result = {}; 108 | 109 | Object.keys(splits).forEach((key) => { 110 | result[key] = { 111 | pattern: this.url_parts[key].original_pattern, 112 | value: splits[key], 113 | result: this.url_parts[key].test(splits[key]) 114 | } 115 | }); 116 | 117 | return result; 118 | } 119 | 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | import complex_url from './complex-url'; 2 | import UrlMatch from './../src/index'; 3 | 4 | 5 | describe('General', function() { 6 | 7 | let url_match; 8 | 9 | beforeEach(function() { 10 | url_match = new UrlMatch(); 11 | }); 12 | 13 | it('should exist', function() { 14 | expect(UrlMatch).toBeDefined(); 15 | }); 16 | 17 | describe('add patterns', function() { 18 | 19 | it('should be created without a pattern', function() { 20 | expect(url_match.patterns.length).toBe(0); 21 | }); 22 | 23 | it('should be created with single pattern', function() { 24 | url_match = new UrlMatch('*'); 25 | expect(url_match.patterns.length).toBe(1); 26 | }); 27 | 28 | it('should be created with multiple patterns', function() { 29 | const patterns = ['*://aaa.bbb/*', '*://ccc.ddd/*', '*://eee.fff/*']; 30 | url_match = new UrlMatch(patterns); 31 | expect(url_match.patterns.length).toBe(3); 32 | }); 33 | 34 | it('should add single pattern', function() { 35 | url_match.add('*'); 36 | expect(url_match.patterns.length).toBe(1); 37 | }); 38 | 39 | it('should add multiple patterns', function() { 40 | url_match.add(['*://aaa.bbb/*', '*://ccc.ddd/*', '*://eee.fff/*']); 41 | expect(url_match.patterns.length).toBe(3); 42 | }); 43 | 44 | it('should only add unique patterns', function() { 45 | url_match = new UrlMatch(['*', '*', '*']); 46 | expect(url_match.patterns.length).toBe(1); 47 | }); 48 | 49 | }); 50 | 51 | describe('remove patterns', function() { 52 | 53 | beforeEach(function() { 54 | const patterns = ['*://aaa.bbb/*', '*://ccc.ddd/*', '*://eee.fff/*']; 55 | url_match = new UrlMatch(patterns); 56 | }); 57 | 58 | it('should remove single pattern', function() { 59 | url_match.remove('*://aaa.bbb/*'); 60 | expect(url_match.patterns.length).toBe(2); 61 | }); 62 | 63 | it('should remove multiple patterns', function() { 64 | url_match.remove(['*://aaa.bbb/*', '*://ccc.ddd/*']); 65 | expect(url_match.patterns.length).toBe(1); 66 | }); 67 | 68 | it('should ignore removing of non-existing patterns', function() { 69 | url_match.remove('*://ggg.hhh/*'); 70 | expect(url_match.patterns.length).toBe(3); 71 | }); 72 | 73 | }); 74 | 75 | describe('matching', function() { 76 | 77 | it('should match URL against a pattern', function() { 78 | const patterns = [ 79 | '*', 80 | '*://*/*?*#*', 81 | 'http://*/*?*#*', 82 | '*://aaa.bbb.ccc/*?*#*', 83 | '*://*/ddd/eee?*#*', 84 | '*://*/*?fff=ggg&hhh=iii#*', 85 | '*://*/*?*#jjj', 86 | complex_url 87 | ]; 88 | 89 | patterns.forEach((pattern) => { 90 | url_match = new UrlMatch(pattern); 91 | expect(url_match.test(complex_url)).toBe(true) 92 | }); 93 | }); 94 | 95 | it('should match URL against all of multiple patterns', function() { 96 | const patterns = ['*://aaa.bbb/*', '*://ccc.ddd/*', '*://eee.fff/*']; 97 | url_match = new UrlMatch(patterns); 98 | expect(url_match.test('http://aaa.bbb/ccc')).toBe(true); 99 | expect(url_match.test('http://xxx.yyy/zzz')).toBe(false); 100 | }); 101 | 102 | }); 103 | 104 | describe('debug', function () { 105 | 106 | it('should return debug object', function () { 107 | const pattern = '*://aaa.bbb/'; 108 | url_match = new UrlMatch(pattern); 109 | const result = url_match.debug('http://aaa.bbb/'); 110 | expect(result[pattern].scheme).toEqual({ 111 | pattern: '*', 112 | value: 'http', 113 | result: true 114 | }); 115 | }); 116 | 117 | }); 118 | 119 | }); 120 | -------------------------------------------------------------------------------- /lib/url-part.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _exists = require('./utilities/exists'); 10 | 11 | var _exists2 = _interopRequireDefault(_exists); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 16 | 17 | var _class = function () { 18 | function _class(pattern) { 19 | _classCallCheck(this, _class); 20 | 21 | this.is_strict = false; 22 | this.original_pattern = pattern; 23 | this.pattern = this.sanitize(pattern); 24 | } 25 | 26 | _class.prototype.validate = function validate() { 27 | var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.original_pattern; 28 | 29 | if ((0, _exists2.default)(pattern)) { 30 | var result = true; 31 | 32 | this.validate_rules.forEach(function (rule) { 33 | if (!rule.test(pattern)) { 34 | result = false; 35 | } 36 | }); 37 | 38 | this.invalidate_rules.forEach(function (rule) { 39 | if (rule.test(pattern)) { 40 | result = false; 41 | } 42 | }); 43 | 44 | return result; 45 | } 46 | 47 | return !this.is_required; 48 | }; 49 | 50 | _class.prototype.test = function test() { 51 | var content = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 52 | var pattern = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.pattern; 53 | 54 | if (content === null) { 55 | content = ''; 56 | } 57 | 58 | if ((0, _exists2.default)(pattern)) { 59 | return pattern.test(content); 60 | } 61 | 62 | return true; 63 | }; 64 | 65 | _class.prototype.sanitize = function sanitize() { 66 | var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.original_pattern; 67 | 68 | if (!(0, _exists2.default)(pattern)) { 69 | pattern = this.default_value; 70 | } 71 | 72 | if ((0, _exists2.default)(pattern) && this.validate(pattern)) { 73 | this.sanitize_replacements.forEach(function (_ref) { 74 | var substring = _ref.substring, 75 | replacement = _ref.replacement; 76 | 77 | pattern = pattern.replace(substring, replacement); 78 | }); 79 | return new RegExp('^' + pattern + '$'); 80 | } 81 | return null; 82 | }; 83 | 84 | _createClass(_class, [{ 85 | key: 'default_value', 86 | get: function get() { 87 | return null; 88 | } 89 | }, { 90 | key: 'is_required', 91 | get: function get() { 92 | return true; 93 | } 94 | }, { 95 | key: 'validate_rules', 96 | get: function get() { 97 | return []; 98 | } 99 | }, { 100 | key: 'invalidate_rules', 101 | get: function get() { 102 | return []; 103 | } 104 | }, { 105 | key: 'sanitize_replacements', 106 | get: function get() { 107 | return []; 108 | } 109 | }]); 110 | 111 | return _class; 112 | }(); 113 | 114 | exports.default = _class; -------------------------------------------------------------------------------- /test/fragment.spec.js: -------------------------------------------------------------------------------- 1 | import Fragment from './../src/fragment'; 2 | 3 | 4 | describe('Fragment', function() { 5 | 6 | let fragment; 7 | 8 | beforeEach(function() { 9 | fragment = new Fragment(); 10 | }); 11 | 12 | describe('validate', function() { 13 | 14 | it('should validate asterisk', function() { 15 | expect(fragment.validate('*')).toBe(true); 16 | }); 17 | 18 | it('should not validate invalid characters', function() { 19 | expect(fragment.validate('#')).toBe(false); 20 | }); 21 | 22 | it('should validate on combination of characters and asterisks', function() { 23 | expect(fragment.validate('aaa*bbb*ccc')).toBe(true); 24 | }); 25 | 26 | }); 27 | 28 | describe('sanitize', function() { 29 | 30 | it('should sanitize single asterisk', function() { 31 | expect(fragment.sanitize('*')).toEqual(/^.*$/); 32 | }); 33 | 34 | it('should sanitize characters with single asterisk', function() { 35 | expect(fragment.sanitize('aaa*')).toEqual(/^aaa.*$/); 36 | expect(fragment.sanitize('*bbb')).toEqual(/^.*bbb$/); 37 | expect(fragment.sanitize('aaa*bbb')).toEqual(/^aaa.*bbb$/); 38 | }); 39 | 40 | it('should sanitize characters with multiple asterisk', function() { 41 | expect(fragment.sanitize('aaa*bbb*ccc')).toEqual(/^aaa.*bbb.*ccc$/); 42 | }); 43 | 44 | it('should escape question mark', function() { 45 | expect(fragment.sanitize('aaa*bbb*ccc')).toEqual(/^aaa.*bbb.*ccc$/); 46 | }); 47 | 48 | }); 49 | 50 | describe('test', function() { 51 | 52 | it('should match empty fragment on universal match', function() { 53 | const pattern = fragment.sanitize('*'); 54 | expect(fragment.test(null, pattern)).toBe(true); 55 | expect(fragment.test('', pattern)).toBe(true); 56 | }); 57 | 58 | it('should match any fragment on universal match', function() { 59 | const pattern = fragment.sanitize('*'); 60 | expect(fragment.test('aaa', pattern)).toBe(true); 61 | }); 62 | 63 | it('should match specific fragment', function() { 64 | const pattern = fragment.sanitize('aaa'); 65 | expect(fragment.test('aaa', pattern)).toBe(true); 66 | }); 67 | 68 | it('should match fragment with single asterisk', function() { 69 | const pattern = fragment.sanitize('aaa*'); 70 | expect(fragment.test('aaa', pattern)).toBe(true); 71 | expect(fragment.test('aaabbb', pattern)).toBe(true); 72 | }); 73 | 74 | it('should not match invalid fragment with single asterisk', function() { 75 | const pattern = fragment.sanitize('aaa*'); 76 | expect(fragment.test('bbb', pattern)).toBe(false); 77 | expect(fragment.test('bbbaaa', pattern)).toBe(false); 78 | }); 79 | 80 | it('should match fragment with multiple asterisks', function() { 81 | const pattern = fragment.sanitize('aaa*bbb*'); 82 | expect(fragment.test('aaabbb', pattern)).toBe(true); 83 | expect(fragment.test('aaaxxxbbbxxx', pattern)).toBe(true); 84 | }); 85 | 86 | it('should not match invalid fragment with multiple asterisks', function() { 87 | const pattern = fragment.sanitize('aaa*bbb*'); 88 | expect(fragment.test('xxx', pattern)).toBe(false); 89 | expect(fragment.test('xxxaaa', pattern)).toBe(false); 90 | expect(fragment.test('xxxaaabbb', pattern)).toBe(false); 91 | }); 92 | 93 | it('should match fragment with question mark', function () { 94 | const pattern = fragment.sanitize('aaa?bbb'); 95 | expect(fragment.test('aaa?bbb', pattern)).toBe(true); 96 | }); 97 | 98 | it('should match fragment with slash', function () { 99 | const pattern = fragment.sanitize('aaa/bbb'); 100 | expect(fragment.test('aaa/bbb', pattern)).toBe(true); 101 | }); 102 | 103 | }); 104 | 105 | }); 106 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # [3.5.0](https://github.com/fczbkk/UrlMatch/compare/v3.4.0...v3.5.0) (2023-05-07) 3 | 4 | 5 | ### Features 6 | 7 | * add type definitions ([9578d07](https://github.com/fczbkk/UrlMatch/commit/9578d07)) 8 | 9 | 10 | 11 | 12 | # [3.4.0](https://github.com/fczbkk/UrlMatch/compare/v3.3.5...v3.4.0) (2023-04-06) 13 | 14 | 15 | ### Features 16 | 17 | * allow to use plus sign in path ([e545f1d](https://github.com/fczbkk/UrlMatch/commit/e545f1d)), closes [#6](https://github.com/fczbkk/UrlMatch/issues/6) 18 | 19 | 20 | 21 | 22 | ## [3.3.5](https://github.com/fczbkk/UrlMatch/compare/v3.3.4...v3.3.5) (2018-04-28) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * allow underscores in domain names ([94c33ca](https://github.com/fczbkk/UrlMatch/commit/94c33ca)) 28 | 29 | 30 | 31 | 32 | ## [3.3.4](https://github.com/fczbkk/UrlMatch/compare/v3.3.3...v3.3.4) (2018-02-08) 33 | 34 | 35 | ### Bug Fixes 36 | 37 | * match pattern with strictly no params ([4a9ecd0](https://github.com/fczbkk/UrlMatch/commit/4a9ecd0)) 38 | 39 | 40 | 41 | 42 | ## [3.3.3](https://github.com/fczbkk/UrlMatch/compare/v3.3.2...v3.3.3) (2017-10-17) 43 | 44 | 45 | ### Bug Fixes 46 | 47 | * escape brackets in path pattern ([d060c1a](https://github.com/fczbkk/UrlMatch/commit/d060c1a)) 48 | 49 | 50 | 51 | 52 | ## [3.3.2](https://github.com/fczbkk/UrlMatch/compare/v3.3.1...v3.3.2) (2017-10-07) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * add valid separator characters to the path part ([05e8e71](https://github.com/fczbkk/UrlMatch/commit/05e8e71)) 58 | 59 | 60 | 61 | 62 | ## [3.3.1](https://github.com/fczbkk/UrlMatch/compare/v3.3.0...v3.3.1) (2017-03-17) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * correctly match patterns containing "@" character([cb3b3fa](https://github.com/fczbkk/UrlMatch/commit/cb3b3fa)) 68 | 69 | 70 | 71 | 72 | # [3.3.0](https://github.com/fczbkk/UrlMatch/compare/v3.2.1...v3.3.0) (2016-10-17) 73 | 74 | 75 | ### Features 76 | 77 | * add `debug()` method([00f1da9](https://github.com/fczbkk/UrlMatch/commit/00f1da9)) 78 | 79 | 80 | 81 | 82 | ## [3.2.1](https://github.com/fczbkk/UrlMatch/compare/v3.2.0...v3.2.1) (2016-07-28) 83 | 84 | 85 | ### Bug Fixes 86 | 87 | * match patterns containing special characters in fragment([4c8b5a1](https://github.com/fczbkk/UrlMatch/commit/4c8b5a1)) 88 | 89 | 90 | 91 | 92 | # [3.2.0](https://github.com/fczbkk/UrlMatch/compare/v3.1.0...v3.2.0) (2016-07-17) 93 | 94 | 95 | ### Bug Fixes 96 | 97 | * revert recent changes([432b5fe](https://github.com/fczbkk/UrlMatch/commit/432b5fe)) 98 | 99 | 100 | 101 | 102 | # [3.1.0](https://github.com/fczbkk/UrlMatch/compare/v3.0.0...v3.1.0) (2016-07-17) 103 | 104 | 105 | ### Bug Fixes 106 | 107 | * assume path always ends with optional trailing slash if missing([0662e33](https://github.com/fczbkk/UrlMatch/commit/0662e33)) 108 | 109 | 110 | 111 | 112 | # [3.0.0](https://github.com/fczbkk/UrlMatch/compare/v2.2.0...v3.0.0) (2016-05-31) 113 | 114 | 115 | 116 | 117 | # [2.2.0](https://github.com/fczbkk/UrlMatch/compare/v2.1.2...v2.2.0) (2016-05-13) 118 | 119 | 120 | ### Features 121 | 122 | * add compatibility with PrototypeJS v1.6 and lower([867bc62](https://github.com/fczbkk/UrlMatch/commit/867bc62)) 123 | 124 | 125 | 126 | 127 | ## [2.1.2](https://github.com/fczbkk/UrlMatch/compare/v2.1.1...v2.1.2) (2016-05-06) 128 | 129 | 130 | ### Bug Fixes 131 | 132 | * do not throw an error in strict Param mode when params are missing([2d62d77](https://github.com/fczbkk/UrlMatch/commit/2d62d77)) 133 | 134 | 135 | 136 | 137 | ## [2.1.1](https://github.com/fczbkk/UrlMatch/compare/v2.1.0...v2.1.1) (2016-05-06) 138 | 139 | 140 | ### Bug Fixes 141 | 142 | * properly remember whether Params should be in strict mode([04f45e7](https://github.com/fczbkk/UrlMatch/commit/04f45e7)) 143 | 144 | 145 | 146 | 147 | # [2.1.0](https://github.com/fczbkk/UrlMatch/compare/v2.0.0...v2.1.0) (2016-05-05) 148 | 149 | 150 | ### Features 151 | 152 | * add compatibility with IE11-([322f16f](https://github.com/fczbkk/UrlMatch/commit/322f16f)) 153 | * add strict mode for params([d2ffa4e](https://github.com/fczbkk/UrlMatch/commit/d2ffa4e)), closes [#1](https://github.com/fczbkk/UrlMatch/issues/1) 154 | 155 | 156 | 157 | 158 | # 2.0.0 (2016-05-01) 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /lib/pattern.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _scheme = require('./scheme'); 8 | 9 | var _scheme2 = _interopRequireDefault(_scheme); 10 | 11 | var _host = require('./host'); 12 | 13 | var _host2 = _interopRequireDefault(_host); 14 | 15 | var _path = require('./path'); 16 | 17 | var _path2 = _interopRequireDefault(_path); 18 | 19 | var _params = require('./params'); 20 | 21 | var _params2 = _interopRequireDefault(_params); 22 | 23 | var _fragment = require('./fragment'); 24 | 25 | var _fragment2 = _interopRequireDefault(_fragment); 26 | 27 | var _exists = require('./utilities/exists'); 28 | 29 | var _exists2 = _interopRequireDefault(_exists); 30 | 31 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 32 | 33 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 34 | 35 | var split_re = new RegExp('^' + // beginning 36 | '([a-z]+|\\*)*' + // (1) scheme 37 | '://' + // scheme separator 38 | '([^\\/\\#\\?]+@)*' + // (2) username and/or password 39 | '([\\w\\*\\.\\-]+)*' + // (3) host 40 | '(\\:\\d+)*' + // (4) port number 41 | '(/([^\\?\\#]*))*' + // (5) path, (6) excluding slash 42 | '(\\?([^\\#]*))*' + // (7) params, (8) excluding question mark 43 | '(\\#(.*))*' // (9) fragment, (10) excluding hash 44 | ); 45 | 46 | var parts_map = { 47 | scheme: 1, 48 | host: 3, 49 | path: 6, 50 | params: 8, 51 | fragment: 10 52 | }; 53 | 54 | var _class = function () { 55 | function _class(pattern) { 56 | _classCallCheck(this, _class); 57 | 58 | if (pattern === '*' || pattern === '') { 59 | pattern = '*://*/*?*#*'; 60 | } 61 | 62 | this.original_pattern = pattern; 63 | this.pattern = this.sanitize(pattern); 64 | this.url_parts = this.getUrlParts(this.pattern); 65 | } 66 | 67 | _class.prototype.split = function split() { 68 | var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 69 | var empty_value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 70 | 71 | var result = {}; 72 | var parts = pattern.match(split_re); 73 | 74 | for (var key in parts_map) { 75 | var val = parts_map[key]; 76 | result[key] = (0, _exists2.default)(parts) && (0, _exists2.default)(parts[val]) ? parts[val] : empty_value; 77 | } 78 | 79 | return result; 80 | }; 81 | 82 | _class.prototype.getUrlParts = function getUrlParts() { 83 | var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.pattern; 84 | 85 | var splits = this.split(pattern); 86 | return { 87 | scheme: new _scheme2.default(splits.scheme), 88 | host: new _host2.default(splits.host), 89 | path: new _path2.default(splits.path), 90 | params: new _params2.default(splits.params), 91 | fragment: new _fragment2.default(splits.fragment) 92 | }; 93 | }; 94 | 95 | _class.prototype.sanitize = function sanitize() { 96 | var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.original_pattern; 97 | 98 | var universal_pattern = '*://*/*?*#*'; 99 | if (pattern === '*' || pattern === '') { 100 | pattern = universal_pattern; 101 | } 102 | return pattern; 103 | }; 104 | 105 | _class.prototype.validate = function validate() { 106 | var url_parts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.url_parts; 107 | 108 | var result = true; 109 | 110 | for (var key in url_parts) { 111 | var val = url_parts[key]; 112 | if (!val.validate()) { 113 | result = false; 114 | } 115 | } 116 | 117 | return result; 118 | }; 119 | 120 | _class.prototype.test = function test(url) { 121 | var _this = this; 122 | 123 | var result = false; 124 | 125 | if ((0, _exists2.default)(url)) { 126 | result = true; 127 | var splits = this.split(url); 128 | ['scheme', 'host', 'path', 'params', 'fragment'].forEach(function (part) { 129 | if (!_this.url_parts[part].test(splits[part])) { 130 | result = false; 131 | } 132 | }); 133 | } 134 | 135 | return result; 136 | }; 137 | 138 | _class.prototype.debug = function debug(url) { 139 | var _this2 = this; 140 | 141 | var splits = this.split(url); 142 | var result = {}; 143 | 144 | Object.keys(splits).forEach(function (key) { 145 | result[key] = { 146 | pattern: _this2.url_parts[key].original_pattern, 147 | value: splits[key], 148 | result: _this2.url_parts[key].test(splits[key]) 149 | }; 150 | }); 151 | 152 | return result; 153 | }; 154 | 155 | return _class; 156 | }(); 157 | 158 | exports.default = _class; -------------------------------------------------------------------------------- /test/host.spec.js: -------------------------------------------------------------------------------- 1 | import Host from './../src/host'; 2 | 3 | 4 | describe('Host', function() { 5 | 6 | let host; 7 | 8 | beforeEach(function() { 9 | host = new Host(); 10 | }); 11 | 12 | describe('validate', function() { 13 | 14 | it('should not validate if empty', function() { 15 | expect(host.validate()).toBe(false); 16 | expect(host.validate('')).toBe(false); 17 | }); 18 | 19 | it('should validate asterisk', function() { 20 | expect(host.validate('*')).toBe(true); 21 | }); 22 | 23 | it('should not validate multiple asterisks', function() { 24 | expect(host.validate('**')).toBe(false); 25 | expect(host.validate('**.aaa')).toBe(false); 26 | expect(host.validate('**.aaa.bbb')).toBe(false); 27 | }); 28 | 29 | it('should validate domain without subdomain', function() { 30 | expect(host.validate('aaa.bbb')).toBe(true); 31 | }); 32 | 33 | it('should validate domain with any level of subdomains', function() { 34 | expect(host.validate('aaa.bbb.ccc')).toBe(true); 35 | expect(host.validate('aaa.bbb.ccc.ddd')).toBe(true); 36 | }); 37 | 38 | it('should validate asterisk at the beginning of domain', function() { 39 | expect(host.validate('*.aaa')).toBe(true); 40 | expect(host.validate('*.aaa.bbb')).toBe(true); 41 | }); 42 | 43 | it('should not validate if asterisk is not followed by a dot', function() { 44 | expect(host.validate('*aaa')).toBe(false); 45 | expect(host.validate('*aaa.bbb')).toBe(false); 46 | }); 47 | 48 | it('should not validate if asterisk is not at the beginning', function() { 49 | expect(host.validate('aaa*')).toBe(false); 50 | expect(host.validate('aaa*bbb')).toBe(false); 51 | }); 52 | 53 | it('should not validate characters except letters, numbers and -', function() { 54 | expect(host.validate('aaa?bbb.ccc')).toBe(false); 55 | expect(host.validate('aaa+bbb.ccc')).toBe(false); 56 | }); 57 | 58 | it('should not validate when starts or ends with dot or hyphen', function() { 59 | expect(host.validate('-aaa.bbb')).toBe(false); 60 | expect(host.validate('.aaa.bbb')).toBe(false); 61 | expect(host.validate('aaa.bbb-')).toBe(false); 62 | expect(host.validate('aaa.bbb.')).toBe(false); 63 | }); 64 | 65 | }); 66 | 67 | describe('sanitize', function() { 68 | 69 | it('should sanitize *', function() { 70 | expect(host.sanitize('*')).toEqual(/^[a-z0-9-_.]+$/); 71 | }); 72 | 73 | it('should sanitize host without asterisk', function() { 74 | expect(host.sanitize('aaa')).toEqual(/^aaa$/); 75 | }); 76 | 77 | it('should sanitize host with asterisk', function() { 78 | expect(host.sanitize('*.aaa')).toEqual(/^([a-z0-9-_.]+\.)?aaa$/); 79 | }); 80 | 81 | }); 82 | 83 | describe('match', function() { 84 | 85 | it('should match any host when * is used', function() { 86 | const pattern = host.sanitize('*'); 87 | expect(host.test('aaa', pattern)).toBe(true); 88 | expect(host.test('aaa.bbb', pattern)).toBe(true); 89 | expect(host.test('aaa.bbb.ccc', pattern)).toBe(true); 90 | }); 91 | 92 | it('should match correct hosts when *.hostname is used', function() { 93 | expect(host.test('aaa.bbb', host.sanitize('*.bbb'))).toBe(true); 94 | expect(host.test('aaa.bbb.ccc', host.sanitize('*.ccc'))).toBe(true); 95 | expect(host.test('aaa.bbb.ccc', host.sanitize('*.bbb.ccc'))).toBe(true); 96 | }); 97 | 98 | it('should not match incorrect hosts when *.hostname is used', function() { 99 | expect(host.test('aaa.bbb', host.sanitize('*.xxx'))).toBe(false); 100 | expect(host.test('aaa.bbb.ccc', host.sanitize('*.xxx'))).toBe(false); 101 | expect(host.test('aaa.bbb.ccc', host.sanitize('*.bbb.xxx'))).toBe(false); 102 | }); 103 | 104 | it('should match correct hosts when specific hostname is used', function() { 105 | expect(host.test('aaa', host.sanitize('aaa'))).toBe(true); 106 | expect(host.test('aaa.bbb', host.sanitize('aaa.bbb'))).toBe(true); 107 | expect(host.test('aaa.bbb.ccc', host.sanitize('aaa.bbb.ccc'))).toBe(true); 108 | }); 109 | 110 | it('should not match incorrect hosts when specific hostname is used', function() { 111 | expect(host.test('aaa', host.sanitize('xxx'))).toBe(false); 112 | expect(host.test('aaa.bbb', host.sanitize('xxx.bbb'))).toBe(false); 113 | expect(host.test('aaa.bbb', host.sanitize('aaa.xxx'))).toBe(false); 114 | expect(host.test('aaa.bbb.ccc', host.sanitize('xxx.bbb.ccc'))).toBe(false); 115 | expect(host.test('aaa.bbb.ccc', host.sanitize('aaa.xxx.ccc'))).toBe(false); 116 | expect(host.test('aaa.bbb.ccc', host.sanitize('aaa.bbb.xxx'))).toBe(false); 117 | }); 118 | 119 | it('should allow underscore in host', function () { 120 | const pattern = host.sanitize('*'); 121 | expect(host.test('aaa_bbb', pattern)).toBe(true); 122 | }); 123 | 124 | }); 125 | 126 | }); 127 | -------------------------------------------------------------------------------- /test/path.spec.js: -------------------------------------------------------------------------------- 1 | import Path from './../src/path'; 2 | 3 | 4 | describe('Path', function() { 5 | 6 | let path; 7 | 8 | beforeEach(function() { 9 | path = new Path(); 10 | }); 11 | 12 | describe('validate', function() { 13 | 14 | it('should validate asterisk', function() { 15 | expect(path.validate('*')).toBe(true); 16 | expect(path.validate('*/*')).toBe(true); 17 | expect(path.validate('aaa*')).toBe(true); 18 | expect(path.validate('*aaa')).toBe(true); 19 | expect(path.validate('aaa*bbb')).toBe(true); 20 | expect(path.validate('*/aaa')).toBe(true); 21 | expect(path.validate('aaa/*')).toBe(true); 22 | expect(path.validate('aaa/*/bbb')).toBe(true); 23 | }); 24 | 25 | return it('should validate specific path', function() { 26 | expect(path.validate('aaa')).toBe(true); 27 | expect(path.validate('aaa/')).toBe(true); 28 | expect(path.validate('aaa/bbb')).toBe(true); 29 | expect(path.validate('aaa/bbb/')).toBe(true); 30 | }); 31 | 32 | }); 33 | 34 | describe('sanitize', function() {}); 35 | 36 | describe('test', function() { 37 | 38 | it('should match any path when * is used', function() { 39 | const pattern = path.sanitize('*'); 40 | expect(path.test('', pattern)).toBe(true); 41 | expect(path.test('/', pattern)).toBe(true); 42 | expect(path.test('aaa', pattern)).toBe(true); 43 | expect(path.test('aaa/', pattern)).toBe(true); 44 | expect(path.test('aaa/bbb', pattern)).toBe(true); 45 | expect(path.test('aaa/bbb/', pattern)).toBe(true); 46 | expect(path.test('aaa/bbb.ccc', pattern)).toBe(true); 47 | }); 48 | 49 | it('should match path containing uppercase letters when * is used', function() { 50 | const pattern = path.sanitize('*'); 51 | expect(path.test('AAA', pattern)).toBe(true); 52 | }); 53 | 54 | it('should match path containing underscore', function() { 55 | const pattern = path.sanitize('*'); 56 | expect(path.test('aaa_bbb', pattern)).toBe(true); 57 | }); 58 | 59 | it('should match correct paths when path with * is specified', function() { 60 | expect(path.test('aaa', path.sanitize('aaa*'))).toBe(true); 61 | expect(path.test('aaa/', path.sanitize('aaa*/'))).toBe(true); 62 | expect(path.test('aaa/', path.sanitize('aaa/*'))).toBe(true); 63 | expect(path.test('aaa/bbb.ccc', path.sanitize('aaa/bbb.ccc*'))).toBe(true); 64 | expect(path.test('aaa/bbb.ccc', path.sanitize('aaa/*.ccc'))).toBe(true); 65 | expect(path.test('aaa/bbb.ccc', path.sanitize('*/*.ccc'))).toBe(true); 66 | }); 67 | 68 | it('should not match incorrect paths when path with * is specified', function() { 69 | expect(path.test('bbb', path.sanitize('aaa*'))).toBe(false); 70 | expect(path.test('bbb/', path.sanitize('aaa/*'))).toBe(false); 71 | expect(path.test('aaa/ccc', path.sanitize('aaa/*.ccc'))).toBe(false); 72 | expect(path.test('bbb.ccc', path.sanitize('*/*.ccc'))).toBe(false); 73 | }); 74 | 75 | it('should assume trailing `/` is optional', function() { 76 | expect(path.test('', path.sanitize('/'))).toBe(true); 77 | expect(path.test('aaa', path.sanitize('aaa/'))).toBe(true); 78 | expect(path.test('aaa/bbb', path.sanitize('aaa/bbb/'))).toBe(true); 79 | }); 80 | 81 | it('should assume trailing `/*` is present when matching', function() { 82 | expect(path.test('', path.sanitize('*'))).toBe(true); 83 | expect(path.test('aaa', path.sanitize('aaa/*'))).toBe(true); 84 | expect(path.test('aaa/bbb', path.sanitize('aaa/bbb/*'))).toBe(true); 85 | }); 86 | 87 | it('should match correct paths specific when path is specified', function() { 88 | expect(path.test('', path.sanitize(''))).toBe(true); 89 | expect(path.test('aaa', path.sanitize('aaa'))).toBe(true); 90 | expect(path.test('aaa/bbb', path.sanitize('aaa/bbb'))).toBe(true); 91 | expect(path.test('aaa/bbb.ccc', path.sanitize('aaa/bbb.ccc'))).toBe(true); 92 | }); 93 | 94 | it('should treat missing path as empty string', function() { 95 | const pattern = path.sanitize(null); 96 | expect(path.test(null, pattern)).toBe(true); 97 | expect(path.test('', pattern)).toBe(true); 98 | expect(path.test('aaa', pattern)).toBe(false); 99 | }); 100 | 101 | it('should match path containing colon', function () { 102 | const pattern = path.sanitize('aaa*bbb'); 103 | expect(path.test('aaa:bbb', pattern)).toBe(true); 104 | }); 105 | 106 | it('should allow to use colon in pattern', function () { 107 | const pattern = path.sanitize('*:*'); 108 | expect(path.test('aaa:bbb', pattern)).toBe(true); 109 | }); 110 | 111 | it('should match path containing plus sign', function () { 112 | const pattern = path.sanitize('aaa*bbb'); 113 | expect(path.test('aaa+bbb', pattern)).toBe(true); 114 | }); 115 | 116 | it('should allow to use plus sign in pattern', function () { 117 | const pattern = path.sanitize('*+*'); 118 | expect(path.test('aaa+bbb', pattern)).toBe(true); 119 | }); 120 | 121 | }); 122 | 123 | }); 124 | -------------------------------------------------------------------------------- /lib/params.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _urlPart = require('./url-part'); 12 | 13 | var _urlPart2 = _interopRequireDefault(_urlPart); 14 | 15 | var _exists = require('./utilities/exists'); 16 | 17 | var _exists2 = _interopRequireDefault(_exists); 18 | 19 | var _arrayReducePrototypejsFix = require('array-reduce-prototypejs-fix'); 20 | 21 | var _arrayReducePrototypejsFix2 = _interopRequireDefault(_arrayReducePrototypejsFix); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 26 | 27 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 28 | 29 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 30 | 31 | var _class = function (_UrlPart) { 32 | _inherits(_class, _UrlPart); 33 | 34 | function _class() { 35 | _classCallCheck(this, _class); 36 | 37 | return _possibleConstructorReturn(this, _UrlPart.apply(this, arguments)); 38 | } 39 | 40 | _class.prototype.sanitize = function sanitize() { 41 | var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.original_pattern; 42 | 43 | // strict mode 44 | if (typeof pattern === 'string' && pattern.substring(0, 1) === '!') { 45 | pattern = pattern.substring(1); 46 | this.is_strict = true; 47 | } 48 | 49 | if (pattern === '*' || pattern === '') { 50 | pattern = null; 51 | } 52 | 53 | var result = []; 54 | 55 | if ((0, _exists2.default)(pattern)) { 56 | 57 | // replace asterisks 58 | pattern.split('&').forEach(function (pair) { 59 | var _pair$split = pair.split('='), 60 | _pair$split2 = _slicedToArray(_pair$split, 2), 61 | key = _pair$split2[0], 62 | val = _pair$split2[1]; 63 | 64 | // if key is asterisk, then at least one character is required 65 | 66 | 67 | key = key === '*' ? '.+' : key.replace(/\*/g, '.*'); 68 | 69 | if (!(0, _exists2.default)(val) || val === '') { 70 | // if value is missing, it is prohibited 71 | // only equal sign is allowed 72 | val = '=?'; 73 | } else { 74 | // if value match is universal, the value is optional 75 | // thus the equal sign is optional 76 | val = val === '*' ? '=?.*' : '=' + val.replace(/\*/g, '.*'); 77 | } 78 | 79 | // escape all brackets 80 | val = val.replace(/[\[\](){}]/g, '\\$&'); 81 | 82 | result.push(key + val); 83 | }); 84 | } 85 | 86 | return result; 87 | }; 88 | 89 | _class.prototype.test = function test() { 90 | var content = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 91 | var patterns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.pattern; 92 | 93 | var result = true; 94 | 95 | if ((0, _exists2.default)(patterns)) { 96 | 97 | // special case, when we want to strictly match no params, e.g. '*://*/*?!' 98 | if (this.is_strict && content === null && patterns.length === 0) { 99 | return true; 100 | } 101 | 102 | result = (0, _arrayReducePrototypejsFix2.default)(patterns, function (previous_result, pattern) { 103 | var re = new RegExp('(^|\&)' + pattern + '(\&|$)'); 104 | return previous_result && re.test(content); 105 | }, result); 106 | 107 | if (this.is_strict === true) { 108 | if (typeof content === 'string') { 109 | var wrapped_patterns = patterns.map(function (pattern) { 110 | return '(' + pattern + ')'; 111 | }).join('|'); 112 | var re = new RegExp('(^|\&)(' + wrapped_patterns + ')(\&|$)'); 113 | 114 | result = (0, _arrayReducePrototypejsFix2.default)(content.split('&'), function (previous_result, pair) { 115 | return previous_result && re.test(pair); 116 | }, result); 117 | } else { 118 | result = false; 119 | } 120 | } 121 | } 122 | 123 | return result; 124 | }; 125 | 126 | _createClass(_class, [{ 127 | key: 'is_required', 128 | get: function get() { 129 | return false; 130 | } 131 | }, { 132 | key: 'invalidate_rules', 133 | get: function get() { 134 | return [ 135 | // two equal signs in a row 136 | /==/, 137 | // equal signs undivided by ampersand 138 | /=[^&]+=/, 139 | // single equal sign 140 | /^=$/]; 141 | } 142 | }]); 143 | 144 | return _class; 145 | }(_urlPart2.default); 146 | 147 | exports.default = _class; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UrlMatch 2 | 3 | JavaScript object that provides URL matching functionality using patterns similar to what is used in extensions in Google Chrome: 4 | http://developer.chrome.com/extensions/match_patterns.html 5 | 6 | On top of Chrome's pattern matching, this object can also check for specific URL parameters and fragments (see below). 7 | 8 | ## How to use 9 | 10 | ### URL validation 11 | 12 | In it's most simple form, you can use it as a generic URL validator. 13 | 14 | ```javascript 15 | myMatch = new UrlMatch('*'); 16 | myMatch.test('http://www.google.com/'); // true 17 | myMatch.test('https://www.google.com/preferences?hl=en'); // true 18 | myMatch.test('this.is.not/valid.url'); // false 19 | ``` 20 | 21 | ### Match against pattern 22 | 23 | But what you usually want to do is to match it against some specific URL pattern. 24 | 25 | ```javascript 26 | myMatch = new UrlMatch('*://*.google.com/*'); 27 | myMatch.test('http://www.google.com/'); // true 28 | myMatch.test('http://calendar.google.com/'); // true 29 | myMatch.test('https://www.google.com/preferences?hl=en'); // true 30 | myMatch.test('http://www.facebook.com/'); // false 31 | myMatch.test('http://www.google.sucks.com/'); // false 32 | ``` 33 | 34 | ## Match against multiple patterns 35 | 36 | You can use it to match against a list of patterns. 37 | 38 | ```javascript 39 | myMatch = new UrlMatch(['*://*.google.com/*', '*://*.facebook.com/*']); 40 | myMatch.test('http://www.google.com/'); // true 41 | myMatch.test('http://www.facebook.com/'); // true 42 | myMatch.test('http://www.apple.com/'); // false 43 | ``` 44 | 45 | ### Add or remove patterns on the fly 46 | 47 | You can add and remove the patterns on the fly. 48 | 49 | ```javascript 50 | myMatch = new UrlMatch('*://*.google.com/*'); 51 | myMatch.test('http://www.google.com/'); // true 52 | myMatch.test('http://www.facebook.com/'); // false 53 | 54 | myMatch.addPattern('*://*.facebook.com/*'); 55 | myMatch.test('http://www.google.com/'); // true 56 | myMatch.test('http://www.facebook.com/'); // true 57 | 58 | myMatch.removePattern('*://*.google.com/*'); 59 | myMatch.test('http://www.google.com/'); // false 60 | myMatch.test('http://www.facebook.com/'); // true 61 | ``` 62 | 63 | ### Match URL paths 64 | 65 | ```javascript 66 | myMatch = new UrlMatch('*://*.google.com/*/preferences'); 67 | myMatch.test('http://www.google.com/preferences'); // false 68 | myMatch.test('http://www.google.com/aaa/preferences'); // true 69 | myMatch.test('http://www.google.com/aaa/preferences/bbb'); // false 70 | ``` 71 | 72 | ### Match URL parameters 73 | 74 | NOTE: This functionality is not available in Chrome pattern matching. 75 | 76 | You can check for URL parameters: 77 | 78 | ```javascript 79 | myMatch = new UrlMatch('*://*/*?aaa=*'); 80 | myMatch.test('http://google.com/?aaa=bbb'); // true 81 | myMatch.test('http://facebook.com/?aaa=ccc'); // true 82 | myMatch.test('http://apple.com/?aaa=bbb&ccc=ddd'); // true 83 | myMatch.test('http://google.com/'); // false 84 | ``` 85 | 86 | You can check for URL parameters with specific value: 87 | 88 | ```javascript 89 | myMatch = new UrlMatch('*://*/*?aaa=bbb'); 90 | myMatch.test('http://google.com/?aaa=bbb'); // true 91 | myMatch.test('http://google.com/?aaa=ccc'); // false 92 | ``` 93 | 94 | You can check for URL parameters using wildcards: 95 | 96 | ```javascript 97 | myMatch = new UrlMatch('*://*/*?aaa=bbb*'); 98 | myMatch.test('http://google.com/?aaa=bbb'); // true 99 | myMatch.test('http://google.com/?aaa=bbbccc'); // true 100 | ``` 101 | 102 | You can even check for any URL parameter with specific value: 103 | 104 | ```javascript 105 | myMatch = new UrlMatch('*://*/*?*=ccc'); 106 | myMatch.test('http://google.com/?aaa=ccc'); // true 107 | myMatch.test('http://google.com/?bbb=ccc'); // true 108 | ``` 109 | 110 | The order of parameters does not matter: 111 | 112 | ```javascript 113 | myMatch = new UrlMatch('*://*/*?aaa=bbb&ccc=ddd'); 114 | myMatch.test('http://google.com/?aaa=bbb&ccc=ddd'); // true 115 | myMatch.test('http://google.com/?ccc=ddd&aaa=bbb'); // true 116 | ``` 117 | 118 | #### Strict mode for params 119 | 120 | You can activate strict mode for checking params by prepending exclamation mark (`!`) in front of params pattern: 121 | 122 | ```javascript 123 | myMatch = new UrlMatch('*://*/*?!aaa=*'); 124 | ``` 125 | 126 | In strict mode, all param patterns must be matched and there can be no unmatched params: 127 | 128 | ```javascript 129 | myMatch.test('http://google.com/?aaa=bbb'); // true 130 | myMatch.test('http://google.com/'); // false (param missing) 131 | myMatch.test('http://google.com/?aaa=bbb&ccc=ddd'); // false (too many params) 132 | ``` 133 | 134 | ### Match URL fragments 135 | 136 | NOTE: This functionality is not available in Chrome pattern matching. 137 | 138 | You can check for URL fragments (the part of the URL after `#`): 139 | 140 | ```javascript 141 | myMatch = new UrlMatch('*://*/*#aaa'); 142 | myMatch.test('http://google.com/#aaa'); // true 143 | myMatch.test('http://google.com/#bbb'); // false 144 | ``` 145 | 146 | You can check for URL fragments using wildcards: 147 | 148 | ```javascript 149 | myMatch = new UrlMatch('*://*/*#aaa*'); 150 | myMatch.test('http://google.com/#aaa'); // true 151 | myMatch.test('http://google.com/#aaabbb'); // true 152 | ``` 153 | 154 | ### Debug 155 | 156 | You can use `debug()` method to get detailed information about matching of each part of the pattern against each tested URL. 157 | 158 | Use it the same way you would use `test()` method. But instead of boolean value, it returns object where keys are tested URLs and values are deconstructed results. 159 | 160 | ```javascript 161 | myMatch = new UrlMatch('*://aaa.bbb/'); 162 | myMatch.debug('http://aaa.bbb/'); 163 | /* 164 | { 165 | "*://aaa.bbb/": { 166 | "scheme": { 167 | "pattern": "*", 168 | "value": "http", 169 | "result": true 170 | }, 171 | "host": { 172 | "pattern": "aaa.bbb", 173 | "value": "aaa.bbb", 174 | "result": true 175 | }, 176 | "path": { 177 | "pattern": "", 178 | "value": "", 179 | "result": true 180 | }, 181 | "params": { 182 | "pattern": null, 183 | "value": null, 184 | "result": true 185 | }, 186 | "fragment": { 187 | "pattern": null, 188 | "value": null, 189 | "result": true 190 | } 191 | } 192 | } 193 | */ 194 | ``` 195 | 196 | ## Bug reports, feature requests and contact 197 | 198 | If you found any bugs, if you have feature requests or any questions, please, either [file an issue at GitHub](https://github.com/fczbkk/UrlMatch/issues) or send me an e-mail at riki@fczbkk.com. 199 | 200 | ## License 201 | 202 | UrlMatch is published under the [MIT license](https://github.com/fczbkk/UrlMatch/blob/master/LICENSE). 203 | -------------------------------------------------------------------------------- /test/real-life-examples.spec.js: -------------------------------------------------------------------------------- 1 | import UrlMatch from './../src/'; 2 | 3 | 4 | describe('Real life examples', function() { 5 | 6 | it('should do universal match', function() { 7 | const my_match = new UrlMatch('*'); 8 | expect(my_match.test('http://www.google.com/')).toBe(true); 9 | expect(my_match.test('https://www.google.com/preferences?hl=en')).toBe(true); 10 | expect(my_match.test('this.is.not/valid.url')).toBe(false); 11 | }); 12 | 13 | it('should do simple match', function() { 14 | const my_match = new UrlMatch('*://*.google.com/*'); 15 | expect(my_match.test('http://www.google.com/')).toBe(true); 16 | expect(my_match.test('http://calendar.google.com/')).toBe(true); 17 | expect(my_match.test('https://www.google.com/preferences?hl=en')).toBe(true); 18 | expect(my_match.test('http://www.facebook.com/')).toBe(false); 19 | expect(my_match.test('http://www.google.sucks.com/')).toBe(false); 20 | }); 21 | 22 | it('should work on URLs with port', function() { 23 | const my_match = new UrlMatch('*://*.google.com/*'); 24 | expect(my_match.test('http://www.google.com:8080/')).toBe(true); 25 | }); 26 | 27 | it('should do match on multiple patterns', function() { 28 | const my_match = new UrlMatch(['*://*.google.com/*', '*://*.facebook.com/*']); 29 | expect(my_match.test('http://www.google.com/')).toBe(true); 30 | expect(my_match.test('http://www.facebook.com/')).toBe(true); 31 | expect(my_match.test('http://www.apple.com/')).toBe(false); 32 | }); 33 | 34 | it('should handle adding and removing of patterns', function() { 35 | const my_match = new UrlMatch('*://*.google.com/*'); 36 | expect(my_match.test('http://www.google.com/')).toBe(true); 37 | expect(my_match.test('http://www.facebook.com/')).toBe(false); 38 | my_match.add('*://*.facebook.com/*'); 39 | expect(my_match.test('http://www.google.com/')).toBe(true); 40 | expect(my_match.test('http://www.facebook.com/')).toBe(true); 41 | my_match.remove('*://*.google.com/*'); 42 | expect(my_match.test('http://www.google.com/')).toBe(false); 43 | expect(my_match.test('http://www.facebook.com/')).toBe(true); 44 | }); 45 | 46 | it('should handle localhost', function() { 47 | const my_match = new UrlMatch('*://*/aaa'); 48 | expect(my_match.test('http://localhost/aaa')).toBe(true); 49 | }); 50 | 51 | it('should handle localhost with port', function() { 52 | const my_match = new UrlMatch('*://*/aaa'); 53 | expect(my_match.test('http://localhost:3000/aaa')).toBe(true); 54 | }); 55 | 56 | it('should not require `/` after a domain name', function() { 57 | const my_match = new UrlMatch('http://google.com/'); 58 | expect(my_match.test('http://google.com')).toBe(true); 59 | }); 60 | 61 | it('should not require `/` after a domain name on general pattern', function() { 62 | const my_match = new UrlMatch('*://*/*'); 63 | expect(my_match.test('http://google.com')).toBe(true); 64 | }); 65 | 66 | it('should match missing 3rd level domain', function() { 67 | const my_match = new UrlMatch('*://*.aaa.com/'); 68 | expect(my_match.test('http://aaa.com/')).toBe(true); 69 | }); 70 | 71 | it('strict matching', function () { 72 | const my_match = new UrlMatch('http://*.aaa.com/*?!aaa=bbb'); 73 | expect(my_match.test('http://www.aaa.com/?aaa=bbb')).toBe(true); 74 | expect(my_match.test('http://www.aaa.com/?aaa=bbb&ccc=ddd')).toBe(false); 75 | expect(my_match.test('http://www.aaa.com/?ccc=ddd')).toBe(false); 76 | expect(my_match.test('http://www.aaa.com/index.php?aaa=bbb')).toBe(true); 77 | }); 78 | 79 | it('should match localhost on universal pattern', function () { 80 | const my_match = new UrlMatch('*'); 81 | expect(my_match.test('http://localhost/')).toBe(true); 82 | expect(my_match.test('http://localhost:3000/')).toBe(true); 83 | expect(my_match.test('http://localhost:3000/aaa/bbb')).toBe(true); 84 | }); 85 | 86 | it('should match localhost on specific pattern', function () { 87 | const my_match = new UrlMatch('*://*/aaa/bbb'); 88 | expect(my_match.test('http://localhost/')).toBe(false); 89 | expect(my_match.test('http://localhost:3000/')).toBe(false); 90 | expect(my_match.test('http://localhost:3000/aaa/bbb')).toBe(true); 91 | }); 92 | 93 | it('should match path commonly used by SPAs', function () { 94 | const pattern = '*://*/sample/context_paths/#/sample?sampleId=1234'; 95 | const url = 96 | 'http://localhost:3000/sample/context_paths/#/sample?sampleId=1234'; 97 | const my_match = new UrlMatch(pattern); 98 | expect(my_match.test(url)).toBe(true); 99 | }); 100 | 101 | it('should match fragment containing @', function () { 102 | const pattern = '*://*/CustomerPortal/#/Page/HOME@folder-35'; 103 | const url = 'http://aaa.bbb/CustomerPortal/#/Page/HOME@folder-35'; 104 | const my_match = new UrlMatch(pattern); 105 | expect(my_match.test(url)).toBe(true); 106 | }); 107 | 108 | it('should work with URLs containing special characters', function () { 109 | // see https://github.com/InlineManual/player/issues/1086 for details 110 | const url = "https://aaa.bbb/ccc/(ddd:eee)"; 111 | const my_match = new UrlMatch('*'); 112 | expect(my_match.test(url)).toBe(true); 113 | }); 114 | 115 | it('should work with patterns containing colon', function () { 116 | // see https://github.com/InlineManual/player/issues/1086 for details 117 | const url = "https://aaa.bbb/ccc:ddd/"; 118 | const my_match = new UrlMatch('*://*/ccc:ddd/'); 119 | expect(my_match.test(url)).toBe(true); 120 | }); 121 | 122 | it('should work with patterns containing brackets', function () { 123 | // see https://github.com/InlineManual/player/issues/1086 for details 124 | const url = "https://aaa.bbb/(ccc)/"; 125 | const my_match = new UrlMatch('*://*/(ccc)/'); 126 | expect(my_match.test(url)).toBe(true); 127 | }); 128 | 129 | it('should work with patterns containing brackets and colon', function () { 130 | // see https://github.com/InlineManual/player/issues/1086 for details 131 | const url = "https://aaa.bbb/(ccc:ddd)/"; 132 | const my_match = new UrlMatch('*://*/(ccc:ddd)/'); 133 | expect(my_match.test(url)).toBe(true); 134 | }); 135 | 136 | it('should allow to match URL with strictly no params', function () { 137 | const my_match = new UrlMatch('*://*/*?!'); 138 | expect(my_match.test('https://aaa.bbb/ccc/')).toBe(true); 139 | expect(my_match.test('https://aaa.bbb/ccc/?')).toBe(true); 140 | expect(my_match.test('https://aaa.bbb/ccc/?ddd')).toBe(false); 141 | expect(my_match.test('https://aaa.bbb/ccc/?ddd=eee')).toBe(false); 142 | }); 143 | 144 | it('should work with patterns containing a plus sign', () => { 145 | const url = 'https://aaa.bbb/ccc+ddd/'; 146 | const my_match = new UrlMatch('*://*/ccc+ddd/'); 147 | expect(my_match.test(url)).toBe(true); 148 | }); 149 | 150 | }); 151 | -------------------------------------------------------------------------------- /test/pattern.spec.js: -------------------------------------------------------------------------------- 1 | import Fragment from './../src/fragment'; 2 | import Host from './../src/host'; 3 | import Params from './../src/params'; 4 | import Path from './../src/path'; 5 | import Pattern from './../src/pattern'; 6 | import Scheme from './../src/scheme' 7 | import complex_url from './complex-url'; 8 | 9 | 10 | describe('Pattern', function() { 11 | let pattern; 12 | 13 | beforeEach(function() { 14 | pattern = new Pattern(); 15 | }); 16 | 17 | describe('split', function() { 18 | 19 | it('should use null for missing parts', function() { 20 | const result = pattern.split('*://*/*'); 21 | expect(result.params).toEqual(null); 22 | expect(result.fragment).toEqual(null); 23 | }); 24 | 25 | it('should use custom value for missing parts', function() { 26 | const result = pattern.split('*://*/*', '*'); 27 | expect(result.params).toEqual('*'); 28 | expect(result.fragment).toEqual('*'); 29 | }); 30 | 31 | it('should split into correct parts', function() { 32 | const result = pattern.split('*://*/*?*#*'); 33 | expect(result.scheme).toEqual('*'); 34 | expect(result.host).toEqual('*'); 35 | expect(result.path).toEqual('*'); 36 | expect(result.params).toEqual('*'); 37 | expect(result.fragment).toEqual('*'); 38 | }); 39 | 40 | it('should split pattern into correct parts', function() { 41 | const result = pattern.split(complex_url); 42 | expect(result.scheme).toEqual('http'); 43 | expect(result.host).toEqual('aaa.bbb.ccc'); 44 | expect(result.path).toEqual('ddd/eee'); 45 | expect(result.params).toEqual('fff=ggg&hhh=iii'); 46 | expect(result.fragment).toEqual('jjj'); 47 | }); 48 | 49 | it('should allow @ in URL', function () { 50 | const result = pattern.split('http://aaa.bbb/ccc@ddd'); 51 | expect(result.host).toEqual('aaa.bbb'); 52 | expect(result.path).toEqual('ccc@ddd'); 53 | }); 54 | 55 | it('should allow username and password and @ in URL', function () { 56 | const result = pattern.split('http://aaa:bbb@ccc.ddd/eee@fff'); 57 | expect(result.host).toEqual('ccc.ddd'); 58 | expect(result.path).toEqual('eee@fff'); 59 | }); 60 | 61 | it('should allow just username and @ in URL', function () { 62 | const result = pattern.split('http://aaa@bbb.ccc/ddd@eee'); 63 | expect(result.host).toEqual('bbb.ccc'); 64 | expect(result.path).toEqual('ddd@eee'); 65 | }); 66 | 67 | it('should allow @ in params', function () { 68 | const result = pattern.split('http://aaa.bbb?ccc=@ddd'); 69 | expect(result.host).toEqual('aaa.bbb'); 70 | expect(result.params).toEqual('ccc=@ddd'); 71 | }); 72 | 73 | it('should allow @ in fragment', function () { 74 | const result = pattern.split('http://aaa.bbb#@ccc'); 75 | expect(result.host).toEqual('aaa.bbb'); 76 | expect(result.fragment).toEqual('@ccc'); 77 | }); 78 | 79 | }); 80 | 81 | describe('getUrlParts', function() { 82 | 83 | let url_parts; 84 | 85 | beforeAll(function () { 86 | url_parts = pattern.getUrlParts('*://*/*?*#*'); 87 | }); 88 | 89 | it('should get scheme', function () { 90 | const obj_re = new Scheme('*').pattern.toString(); 91 | const ref_re = url_parts['scheme'].pattern.toString(); 92 | expect(obj_re).toEqual(ref_re); 93 | }); 94 | 95 | it('should get host', function () { 96 | const obj_re = new Host('*').pattern.toString(); 97 | const ref_re = url_parts['host'].pattern.toString(); 98 | expect(obj_re).toEqual(ref_re); 99 | }); 100 | 101 | it('should get path', function () { 102 | const obj_re = new Path('*').pattern.toString(); 103 | const ref_re = url_parts['path'].pattern.toString(); 104 | expect(obj_re).toEqual(ref_re); 105 | }); 106 | 107 | it('should get params', function () { 108 | const obj_re = new Params('*').pattern.toString(); 109 | const ref_re = url_parts['params'].pattern.toString(); 110 | expect(obj_re).toEqual(ref_re); 111 | }); 112 | 113 | it('should get fragment', function () { 114 | const obj_re = new Fragment('*').pattern.toString(); 115 | const ref_re = url_parts['fragment'].pattern.toString(); 116 | expect(obj_re).toEqual(ref_re); 117 | }); 118 | 119 | }); 120 | 121 | describe('validate', function() { 122 | 123 | let validatePattern; 124 | 125 | beforeAll(function () { 126 | 127 | validatePattern = function(content = '', expected_value = true) { 128 | const url_parts = pattern.getUrlParts(content); 129 | expect(pattern.validate(url_parts)).toBe(expected_value); 130 | }; 131 | 132 | }); 133 | 134 | describe('valid patterns', function() { 135 | 136 | it('should validate universal pattern', function() { 137 | validatePattern('*://*/*?*#*', true); 138 | }); 139 | 140 | it('should validate pattern with empty path', function() { 141 | validatePattern('*://*/', true); 142 | }); 143 | 144 | it('should validate pattern with just scheme, host and path', function() { 145 | validatePattern('*://*/*', true); 146 | validatePattern('http://aaa.bbb/ccc', true); 147 | }); 148 | 149 | it('should validate pattern with params', function() { 150 | validatePattern('*://*/*?aaa=bbb', true); 151 | }); 152 | 153 | it('should validate pattern with fragment', function() { 154 | validatePattern('*://*/*#aaa', true); 155 | }); 156 | 157 | it('should validate full pattern', function() { 158 | validatePattern('*://*/*?*#*', true); 159 | validatePattern(complex_url, true); 160 | }); 161 | 162 | }); 163 | 164 | describe('incomplete patterns', function() { 165 | 166 | it('should not validate pattern without scheme', function() { 167 | validatePattern('*/*', false); 168 | validatePattern('://*/*', false); 169 | validatePattern('://aaa.bbb/', false); 170 | validatePattern('aaa.bbb/', false); 171 | }); 172 | 173 | it('should not validate pattern without host', function() { 174 | validatePattern('*:///*', false); 175 | validatePattern('http:///aaa', false); 176 | }); 177 | }); 178 | 179 | describe('invalid patterns', function() { 180 | 181 | it('should not validate pattern with invalid scheme', function() { 182 | validatePattern('http//*/*', false); 183 | validatePattern('http:/*/*', false); 184 | validatePattern('http:*/*', false); 185 | validatePattern('http*/*', false); 186 | }); 187 | 188 | it('should not validate pattern with invalid host', function() { 189 | validatePattern('http://**/*', false); 190 | validatePattern('http://aaa*bbb/*', false); 191 | validatePattern('http://aaa.*/*', false); 192 | }); 193 | 194 | it('should not validate pattern with invalid params', function() { 195 | validatePattern('*://*/*?aaa==bbb', false); 196 | validatePattern('*://*/*?aaa=bbb=ccc', false); 197 | validatePattern('*://*/*?=', false); 198 | }); 199 | 200 | it('should not validate pattern with invalid fragment', function() { 201 | validatePattern('*://*/*##', false); 202 | validatePattern('*://*/*##aaa', false); 203 | validatePattern('*://*/*#aaa#', false); 204 | validatePattern('*://*/*#aaa#bbb', false); 205 | }); 206 | 207 | }); 208 | 209 | }); 210 | 211 | describe('sanitize', function() { 212 | 213 | it('should convert single asterisk to universal match pattern', function() { 214 | pattern = new Pattern('*'); 215 | expect(pattern.original_pattern).toEqual('*://*/*?*#*'); 216 | }); 217 | 218 | it('should convert `` to universal match pattern', function() { 219 | pattern = new Pattern(''); 220 | expect(pattern.original_pattern).toEqual('*://*/*?*#*'); 221 | }); 222 | 223 | }); 224 | 225 | describe('match', function() { 226 | 227 | it('should match any URL', function() { 228 | pattern = new Pattern('*://*/*?*#*'); 229 | expect(pattern.test(complex_url)).toBe(true); 230 | }); 231 | 232 | it('should match correct scheme', function() { 233 | pattern = new Pattern('http://*/*?*#*'); 234 | expect(pattern.test(complex_url)).toBe(true); 235 | }); 236 | 237 | it('should match correct host', function() { 238 | pattern = new Pattern('*://aaa.bbb.ccc/*?*#*'); 239 | expect(pattern.test(complex_url)).toBe(true); 240 | }); 241 | 242 | it('should match correct path', function() { 243 | pattern = new Pattern('*://*/ddd/eee?*#*'); 244 | expect(pattern.test(complex_url)).toBe(true); 245 | }); 246 | 247 | it('should match correct params', function() { 248 | pattern = new Pattern('*://*/*?fff=ggg&hhh=iii#*'); 249 | expect(pattern.test(complex_url)).toBe(true); 250 | }); 251 | 252 | it('should match correct fragment', function() { 253 | pattern = new Pattern('*://*/*?*#jjj'); 254 | expect(pattern.test(complex_url)).toBe(true); 255 | }); 256 | 257 | it('should match exact URL', function() { 258 | pattern = new Pattern(complex_url); 259 | expect(pattern.test(complex_url)).toBe(true); 260 | }); 261 | 262 | it('should match pattern without path correctly', function() { 263 | pattern = new Pattern('*://aaa.bbb/'); 264 | expect(pattern.test('http://aaa.bbb/')).toBe(true); 265 | expect(pattern.test('http://aaa.bbb/ccc')).toBe(false); 266 | }); 267 | 268 | }); 269 | 270 | describe('debug', function () { 271 | 272 | it('should return debug object', function () { 273 | pattern = new Pattern('*://aaa.bbb/'); 274 | const result = pattern.debug('http://aaa.bbb/'); 275 | expect(result.scheme) 276 | .toEqual({pattern: '*', value: 'http', result: true}); 277 | }); 278 | 279 | }); 280 | 281 | }); 282 | -------------------------------------------------------------------------------- /test/params.spec.js: -------------------------------------------------------------------------------- 1 | import Params from './../src/params'; 2 | 3 | 4 | describe('Params', function() { 5 | 6 | let params; 7 | 8 | beforeEach(function() { 9 | params = new Params(); 10 | }); 11 | 12 | describe('validate', function() { 13 | 14 | it('should validate asterisk', function() { 15 | expect(params.validate('*')).toBe(true); 16 | }); 17 | 18 | it('should validate full pair (key and value defined)', function() { 19 | expect(params.validate('aaa=bbb')).toBe(true); 20 | }); 21 | 22 | it('should validate valueless param', function() { 23 | expect(params.validate('aaa')).toBe(true); 24 | expect(params.validate('aaa=bbb&ccc')).toBe(true); 25 | }); 26 | 27 | it('should validate on multiple pairs', function() { 28 | expect(params.validate('aaa=bbb&ccc=ddd')).toBe(true); 29 | expect(params.validate('aaa=bbb&ccc=ddd&eee=fff')).toBe(true); 30 | }); 31 | 32 | it('should validate on pair with asterisk instead of key or value', function() { 33 | expect(params.validate('*=*')).toBe(true); 34 | expect(params.validate('aaa=*')).toBe(true); 35 | expect(params.validate('*=aaa')).toBe(true); 36 | expect(params.validate('aaa=*&*=bbb')).toBe(true); 37 | }); 38 | 39 | it('should not validate multiple equal signs', function() { 40 | expect(params.validate('==')).toBe(false); 41 | expect(params.validate('aaa==bbb')).toBe(false); 42 | }); 43 | 44 | it('should not validate pairs undivided by ampersand', function() { 45 | expect(params.validate('aaa=bbb=')).toBe(false); 46 | expect(params.validate('=aaa=bbb')).toBe(false); 47 | expect(params.validate('aaa=bbb=ccc')).toBe(false); 48 | }); 49 | 50 | it('should not validate an asterisk sandwitch', function() { 51 | expect(params.validate('aaa=*=bbb')).toBe(false); 52 | }); 53 | 54 | it('should not validate single equal sign', function() { 55 | expect(params.validate('=')).toBe(false); 56 | }); 57 | 58 | }); 59 | 60 | describe('sanitize', function() { 61 | 62 | it('should return empty hash on empty', function() { 63 | expect(params.sanitize()).toEqual([]); 64 | }); 65 | 66 | it('should return empty hash on single asterisk', function() { 67 | expect(params.sanitize('*')).toEqual([]); 68 | }); 69 | 70 | it('should handle valueless pair', function() { 71 | expect(params.sanitize('aaa')).toEqual([ 72 | 'aaa=?' 73 | ]); 74 | expect(params.sanitize('aaa=')).toEqual([ 75 | 'aaa=?' 76 | ]); 77 | }); 78 | 79 | it('should break the single pair pattern down to key/val pairs', function() { 80 | expect(params.sanitize('aaa=bbb')).toEqual([ 81 | 'aaa=bbb' 82 | ]); 83 | }); 84 | 85 | it('should break the multi pair pattern down to key/val pairs', function() { 86 | expect(params.sanitize('aaa=bbb&ccc=ddd')).toEqual([ 87 | 'aaa=bbb', 88 | 'ccc=ddd' 89 | ]); 90 | }); 91 | 92 | it('should replace asterisks in keys with universal matches', function() { 93 | expect(params.sanitize('*=bbb')).toEqual([ 94 | '.+=bbb' 95 | ]); 96 | }); 97 | 98 | it('should replace asterisks in vals with universal matches', function() { 99 | expect(params.sanitize('aaa=*')).toEqual([ 100 | 'aaa=?.*' 101 | ]); 102 | }); 103 | 104 | it('should replace partial asterisks with universal matches', function() { 105 | expect(params.sanitize('aaa*=*bbb&ccc*ddd=*eee*')).toEqual([ 106 | 'aaa.*=.*bbb', 107 | 'ccc.*ddd=.*eee.*' 108 | ]); 109 | }); 110 | 111 | it('should escape square brackets', function() { 112 | expect(params.sanitize('aaa=[]')).toEqual([ 113 | 'aaa=\\[\\]' 114 | ]); 115 | }); 116 | 117 | it('should escape nested square brackets', function() { 118 | expect(params.sanitize('aaa=[[]]')).toEqual([ 119 | 'aaa=\\[\\[\\]\\]' 120 | ]); 121 | }); 122 | 123 | it('should escape serialized JSON data', function() { 124 | expect(params.sanitize('aaa=[[]]')).toEqual([ 125 | 'aaa=\\[\\[\\]\\]' 126 | ]); 127 | }); 128 | 129 | }); 130 | 131 | describe('test', function() { 132 | 133 | it('should match empty params on universal match', function() { 134 | const pattern = params.sanitize('*'); 135 | expect(params.test('', pattern)).toBe(true); 136 | }); 137 | 138 | it('should match any params on universal match', function() { 139 | const pattern = params.sanitize('*'); 140 | expect(params.test('aaa', pattern)).toBe(true); 141 | expect(params.test('aaa=bbb', pattern)).toBe(true); 142 | expect(params.test('aaa=bbb&ccc=ddd', pattern)).toBe(true); 143 | }); 144 | 145 | it('should match at least one param on asterisk to asterisk match', function() { 146 | const pattern = params.sanitize('*=*'); 147 | expect(params.test('', pattern)).toBe(false); 148 | expect(params.test('aaa', pattern)).toBe(true); 149 | expect(params.test('aaa=bbb', pattern)).toBe(true); 150 | expect(params.test('aaa=bbb&ccc=ddd', pattern)).toBe(true); 151 | }); 152 | 153 | it('should match single key/val pair', function() { 154 | const pattern = params.sanitize('aaa=bbb'); 155 | 156 | expect(params.test('aaa=bbb', pattern)).toBe(true); 157 | 158 | expect(params.test('aaa', pattern)).toBe(false); 159 | expect(params.test('bbb', pattern)).toBe(false); 160 | expect(params.test('bbb=aaa', pattern)).toBe(false); 161 | expect(params.test('ccc=ddd', pattern)).toBe(false); 162 | }); 163 | 164 | it('should match single key/val pair among many pairs', function() { 165 | const pattern = params.sanitize('aaa=bbb'); 166 | 167 | expect(params.test('aaa=bbb&ccc=ddd', pattern)).toBe(true); 168 | expect(params.test('ccc=ddd&aaa=bbb&eee=fff', pattern)).toBe(true); 169 | 170 | expect(params.test('bbb=aaa&ccc=ddd', pattern)).toBe(false); 171 | expect(params.test('ccc=ddd&eee=fff&ggg=hhh', pattern)).toBe(false); 172 | }); 173 | 174 | it('should match multiple key/val pairs in any order', function() { 175 | const pattern = params.sanitize('aaa=bbb&ccc=ddd'); 176 | expect(params.test('ccc=ddd&aaa=bbb', pattern)).toBe(true); 177 | expect(params.test('eee=fff&aaa=bbb&ccc=ddd', pattern)).toBe(true); 178 | }); 179 | 180 | it('should match pair with universal key', function() { 181 | const pattern = params.sanitize('aaa=*'); 182 | expect(params.test('aaa=bbb', pattern)).toBe(true); 183 | }); 184 | 185 | it('should match pair with universal val', function() { 186 | const pattern = params.sanitize('*=bbb'); 187 | expect(params.test('aaa=bbb', pattern)).toBe(true); 188 | }); 189 | 190 | it('should match partial wildcard in key', function() { 191 | const pattern = params.sanitize('aaa*ccc=ddd'); 192 | expect(params.test('aaabbbccc=ddd', pattern)).toBe(true); 193 | }); 194 | 195 | it('should match partial wildcard in val', function() { 196 | const pattern = params.sanitize('aaa=bbb*ddd'); 197 | expect(params.test('aaa=bbbcccddd', pattern)).toBe(true); 198 | }); 199 | 200 | it('should match val with square brackets', function() { 201 | const pattern = params.sanitize('aaa=[bbb]'); 202 | expect(params.test('aaa=[bbb]', pattern)).toBe(true); 203 | }); 204 | 205 | it('should match val with asterisk in square brackets', function() { 206 | const pattern = params.sanitize('aaa=bbb[*]ddd'); 207 | expect(params.test('aaa=bbb[ccc]ddd', pattern)).toBe(true); 208 | }); 209 | 210 | it('should match val with nested brackets', function() { 211 | const pattern = params.sanitize('aaa=[[*]]'); 212 | expect(params.test('aaa=[[bbb]]', pattern)).toBe(true); 213 | }); 214 | 215 | it('should match val with serialized JSON data', function() { 216 | const pattern = params.sanitize('aaa={bbb:*,ddd:[*,fff]}'); 217 | expect(params.test('aaa={bbb:ccc,ddd:[eee,fff]}', pattern)).toBe(true); 218 | }); 219 | 220 | it('should match unwanted val', function() { 221 | let pattern; 222 | 223 | pattern = params.sanitize('aaa'); 224 | expect(params.test('aaa', pattern)).toBe(true); 225 | expect(params.test('aaa=', pattern)).toBe(true); 226 | expect(params.test('aaa=bbb', pattern)).toBe(false); 227 | 228 | pattern = params.sanitize('aaa='); 229 | expect(params.test('aaa', pattern)).toBe(true); 230 | expect(params.test('aaa=', pattern)).toBe(true); 231 | expect(params.test('aaa=bbb', pattern)).toBe(false); 232 | }); 233 | 234 | }); 235 | 236 | describe('strict mode', function () { 237 | 238 | it('should set `is_strict` property', function () { 239 | params.sanitize('!aaa=*'); 240 | expect(params.is_strict).toEqual(true); 241 | }); 242 | 243 | it('should match known params when in strict mode', function () { 244 | const pattern = params.sanitize('!aaa=*'); 245 | expect(params.test('aaa=bbb', pattern)).toBe(true); 246 | }); 247 | 248 | it('should not match unknown params when in strict mode', function () { 249 | const pattern = params.sanitize('!aaa=*'); 250 | expect(params.test('aaa=bbb&ccc=ddd', pattern)).toBe(false); 251 | expect(params.test('ccc=ddd', pattern)).toBe(false); 252 | }); 253 | 254 | it('should not match any params when empty in strict mode', function () { 255 | const pattern = params.sanitize('!'); 256 | expect(params.test('aaa=bbb', pattern)).toBe(false); 257 | }); 258 | 259 | it('should match empty params when empty in strict mode', function () { 260 | const pattern = params.sanitize('!'); 261 | expect(params.test('', pattern)).toBe(true); 262 | }); 263 | 264 | it('should match empty params when null in strict mode', function () { 265 | const pattern = params.sanitize('!'); 266 | expect(params.test(null, pattern)).toBe(true); 267 | }); 268 | 269 | it('should match generic key param in strict mode', function () { 270 | const pattern = params.sanitize('!*=aaa'); 271 | expect(params.test('bbb=aaa', pattern)).toBe(true); 272 | expect(params.test('ccc=aaa', pattern)).toBe(true); 273 | }); 274 | 275 | it('should match multiple key params in strict mode', function () { 276 | const pattern = params.sanitize('!*=aaa'); 277 | expect(params.test('bbb=aaa&ccc=aaa', pattern)).toBe(true); 278 | }); 279 | 280 | it('should match multiple generic key params in strict mode', function () { 281 | const pattern = params.sanitize('!*=aaa&*=ccc'); 282 | expect(params.test('bbb=aaa&bbb=ccc', pattern)).toBe(true); 283 | expect(params.test('bbb=aaa&bbb=ccc&xxx=yyy', pattern)).toBe(false); 284 | }); 285 | 286 | it('should match multiple val params in strict mode', function () { 287 | const pattern = params.sanitize('!aaa=*'); 288 | expect(params.test('aaa=bbb&aaa=ccc', pattern)).toBe(true); 289 | }); 290 | 291 | it('should not match one of multiple params in strict mode', function () { 292 | const pattern = params.sanitize('!aaa=*ccc'); 293 | expect(params.test('aaa=bbbccc&aaa=xxx', pattern)).toBe(false); 294 | }); 295 | 296 | it('should work without errors when no params are present', function () { 297 | const pattern = params.sanitize('!aaa=bbb'); 298 | expect(params.test(null, pattern)).toBe(false); 299 | }); 300 | 301 | }); 302 | }); 303 | -------------------------------------------------------------------------------- /docs/url-match.js: -------------------------------------------------------------------------------- 1 | var UrlMatch=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=13)}([function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:this.original_pattern;if((0,u.default)(t)){var e=!0;return this.validate_rules.forEach(function(n){n.test(t)||(e=!1)}),this.invalidate_rules.forEach(function(n){n.test(t)&&(e=!1)}),e}return!this.is_required},t.prototype.test=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.pattern;return null===t&&(t=""),!(0,u.default)(e)||e.test(t)},t.prototype.sanitize=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.original_pattern;return(0,u.default)(t)||(t=this.default_value),(0,u.default)(t)&&this.validate(t)?(this.sanitize_replacements.forEach(function(e){var n=e.substring,r=e.replacement;t=t.replace(n,r)}),new RegExp("^"+t+"$")):null},o(t,[{key:"default_value",get:function(){return null}},{key:"is_required",get:function(){return!0}},{key:"validate_rules",get:function(){return[]}},{key:"invalidate_rules",get:function(){return[]}},{key:"sanitize_replacements",get:function(){return[]}}]),t}();e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){return void 0!==t&&null!==t}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n=0;c--)if(p[c]!=y[c])return!1;for(c=p.length-1;c>=0;c--)if(s=p[c],!a(t[s],e[s],n))return!1;return(void 0===t?"undefined":r(t))===(void 0===e?"undefined":r(e))}(t,e,n))};function f(t){return null===t||void 0===t}function l(t){return!(!t||"object"!==(void 0===t?"undefined":r(t))||"number"!=typeof t.length)&&("function"==typeof t.copy&&"function"==typeof t.slice&&!(t.length>0&&"number"!=typeof t[0]))}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){if(null==this)throw new TypeError("Array.prototype.reduce called on null or undefined");if("function"!=typeof t)throw new TypeError(t+" is not a function");var e,n=Object(this),r=n.length>>>0,o=0;if(2==arguments.length)e=arguments[1];else{for(;o=r)throw new TypeError("Reduce of empty array with no initial value");e=n[o++]}for(;o1?n-1:0),u=1;u0&&void 0!==arguments[0]?arguments[0]:this.original_pattern;"string"==typeof t&&"!"===t.substring(0,1)&&(t=t.substring(1),this.is_strict=!0),"*"!==t&&""!==t||(t=null);var e=[];return(0,u.default)(t)&&t.split("&").forEach(function(t){var n=t.split("="),o=r(n,2),i=o[0],a=o[1];i="*"===i?".+":i.replace(/\*/g,".*"),a=(a=(0,u.default)(a)&&""!==a?"*"===a?"=?.*":"="+a.replace(/\*/g,".*"):"=?").replace(/[\[\](){}]/g,"\\$&"),e.push(i+a)}),e},e.prototype.test=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.pattern,n=!0;if((0,u.default)(e)){if(this.is_strict&&null===t&&0===e.length)return!0;if(n=(0,a.default)(e,function(e,n){var r=new RegExp("(^|&)"+n+"(&|$)");return e&&r.test(t)},n),!0===this.is_strict)if("string"==typeof t){var r=e.map(function(t){return"("+t+")"}).join("|"),o=new RegExp("(^|&)("+r+")(&|$)");n=(0,a.default)(t.split("&"),function(t,e){return t&&o.test(e)},n)}else n=!1}return n},o(e,[{key:"is_required",get:function(){return!1}},{key:"invalidate_rules",get:function(){return[/==/,/=[^&]+=/,/^=$/]}}]),e}(i.default);e.default=l},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:this.original_pattern;return!!(0,i.default)(t)&&new RegExp("^(\\*|[a-z]+)$").test(t)},r(e,[{key:"sanitize_replacements",get:function(){return[{substring:"*",replacement:"https?"}]}}]),e}(o.default);e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=l(n(11)),o=l(n(10)),i=l(n(9)),u=l(n(8)),a=l(n(2)),f=l(n(1));function l(t){return t&&t.__esModule?t:{default:t}}var c=new RegExp("^([a-z]+|\\*)*://([^\\/\\#\\?]+@)*([\\w\\*\\.\\-]+)*(\\:\\d+)*(/([^\\?\\#]*))*(\\?([^\\#]*))*(\\#(.*))*"),s={scheme:1,host:3,path:6,params:8,fragment:10},p=function(){function t(e){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),"*"!==e&&""!==e||(e="*://*/*?*#*"),this.original_pattern=e,this.pattern=this.sanitize(e),this.url_parts=this.getUrlParts(this.pattern)}return t.prototype.split=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n={},r=t.match(c);for(var o in s){var i=s[o];n[o]=(0,f.default)(r)&&(0,f.default)(r[i])?r[i]:e}return n},t.prototype.getUrlParts=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.pattern,e=this.split(t);return{scheme:new r.default(e.scheme),host:new o.default(e.host),path:new i.default(e.path),params:new u.default(e.params),fragment:new a.default(e.fragment)}},t.prototype.sanitize=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.original_pattern;return"*"!==t&&""!==t||(t="*://*/*?*#*"),t},t.prototype.validate=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.url_parts,e=!0;for(var n in t){t[n].validate()||(e=!1)}return e},t.prototype.test=function(t){var e=this,n=!1;if((0,f.default)(t)){n=!0;var r=this.split(t);["scheme","host","path","params","fragment"].forEach(function(t){e.url_parts[t].test(r[t])||(n=!1)})}return n},t.prototype.debug=function(t){var e=this,n=this.split(t),r={};return Object.keys(n).forEach(function(t){r[t]={pattern:e.url_parts[t].original_pattern,value:n[t],result:e.url_parts[t].test(n[t])}}),r},t}();e.default=p},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=n(12),i=(r=o)&&r.__esModule?r:{default:r};var u=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.patterns=[],this.add(e)}return t.prototype.add=function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return"string"==typeof e&&(e=[e]),e.forEach(function(e){-1===t.patterns.indexOf(e)&&t.patterns.push(e)}),this.patterns},t.prototype.remove=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return"string"==typeof t&&(t=[t]),this.patterns=this.patterns.filter(function(e){return-1===t.indexOf(e)}),this.patterns},t.prototype.test=function(t){var e=!1;return this.patterns.forEach(function(n){!0===new i.default(n).test(t)&&(e=!0)}),e},t.prototype.debug=function(t){var e={};return this.patterns.forEach(function(n){var r=new i.default(n);e[n]=r.debug(t)}),e},t}();e.default=u}]); --------------------------------------------------------------------------------