1 | 'use strict';
|
2 |
|
3 | const URLParser = typeof URL === 'undefined' ? require('url').URL : URL;
|
4 |
|
5 | function testParameter(name, filters) {
|
6 | return filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name);
|
7 | }
|
8 |
|
9 | module.exports = (urlString, opts) => {
|
10 | opts = Object.assign({
|
11 | normalizeProtocol: true,
|
12 | normalizeHttps: false,
|
13 | stripFragment: true,
|
14 | stripWWW: true,
|
15 | removeQueryParameters: [/^utm_\w+/i],
|
16 | removeTrailingSlash: true,
|
17 | removeDirectoryIndex: false,
|
18 | sortQueryParameters: true
|
19 | }, opts);
|
20 |
|
21 | urlString = urlString.trim();
|
22 |
|
23 | const hasRelativeProtocol = urlString.startsWith('//');
|
24 | const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString);
|
25 |
|
26 |
|
27 | if (!isRelativeUrl) {
|
28 | urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, 'http://');
|
29 | }
|
30 |
|
31 | const urlObj = new URLParser(urlString);
|
32 |
|
33 | if (opts.normalizeHttps && urlObj.protocol === 'https:') {
|
34 | urlObj.protocol = 'http:';
|
35 | }
|
36 |
|
37 |
|
38 | if (opts.stripFragment) {
|
39 | urlObj.hash = '';
|
40 | }
|
41 |
|
42 |
|
43 | if (urlObj.pathname) {
|
44 | urlObj.pathname = urlObj.pathname.replace(/\/{2,}/g, '/');
|
45 | }
|
46 |
|
47 |
|
48 | if (urlObj.pathname) {
|
49 | urlObj.pathname = decodeURI(urlObj.pathname);
|
50 | }
|
51 |
|
52 |
|
53 | if (opts.removeDirectoryIndex === true) {
|
54 | opts.removeDirectoryIndex = [/^index\.[a-z]+$/];
|
55 | }
|
56 |
|
57 | if (Array.isArray(opts.removeDirectoryIndex) && opts.removeDirectoryIndex.length > 0) {
|
58 | let pathComponents = urlObj.pathname.split('/');
|
59 | const lastComponent = pathComponents[pathComponents.length - 1];
|
60 |
|
61 | if (testParameter(lastComponent, opts.removeDirectoryIndex)) {
|
62 | pathComponents = pathComponents.slice(0, pathComponents.length - 1);
|
63 | urlObj.pathname = pathComponents.slice(1).join('/') + '/';
|
64 | }
|
65 | }
|
66 |
|
67 | if (urlObj.hostname) {
|
68 |
|
69 | urlObj.hostname = urlObj.hostname.replace(/\.$/, '');
|
70 |
|
71 |
|
72 | if (opts.stripWWW) {
|
73 | urlObj.hostname = urlObj.hostname.replace(/^www\./, '');
|
74 | }
|
75 | }
|
76 |
|
77 |
|
78 | if (Array.isArray(opts.removeQueryParameters)) {
|
79 | for (const key of [...urlObj.searchParams.keys()]) {
|
80 | if (testParameter(key, opts.removeQueryParameters)) {
|
81 | urlObj.searchParams.delete(key);
|
82 | }
|
83 | }
|
84 | }
|
85 |
|
86 |
|
87 | if (opts.sortQueryParameters) {
|
88 | urlObj.searchParams.sort();
|
89 | }
|
90 |
|
91 |
|
92 | urlString = urlObj.toString();
|
93 |
|
94 |
|
95 | if (opts.removeTrailingSlash || urlObj.pathname === '/') {
|
96 | urlString = urlString.replace(/\/$/, '');
|
97 | }
|
98 |
|
99 |
|
100 | if (hasRelativeProtocol && !opts.normalizeProtocol) {
|
101 | urlString = urlString.replace(/^http:\/\//, '//');
|
102 | }
|
103 |
|
104 | return urlString;
|
105 | };
|