1 | /* istanbul ignore next */
|
---|
2 | const Url = (typeof URL !== 'undefined' ? URL : require('url').URL);
|
---|
3 | // Matches "..", which must be preceeded by "/" or the start of the string, and
|
---|
4 | // must be followed by a "/". We do not eat the following "/", so that the next
|
---|
5 | // iteration can match on it.
|
---|
6 | const parentRegex = /(^|\/)\.\.(?=\/|$)/g;
|
---|
7 | function isAbsoluteUrl(url) {
|
---|
8 | try {
|
---|
9 | return !!new Url(url);
|
---|
10 | }
|
---|
11 | catch (e) {
|
---|
12 | return false;
|
---|
13 | }
|
---|
14 | }
|
---|
15 | /**
|
---|
16 | * Creates a directory name that is guaranteed to not be in `str`.
|
---|
17 | */
|
---|
18 | function uniqInStr(str) {
|
---|
19 | let uniq = String(Math.random()).slice(2);
|
---|
20 | while (str.indexOf(uniq) > -1) {
|
---|
21 | /* istanbul ignore next */
|
---|
22 | uniq += uniq;
|
---|
23 | }
|
---|
24 | return uniq;
|
---|
25 | }
|
---|
26 | /**
|
---|
27 | * Removes the filename from the path (everything trailing the last "/"). This
|
---|
28 | * is only safe to call on a path, never call with an absolute or protocol
|
---|
29 | * relative URL.
|
---|
30 | */
|
---|
31 | function stripPathFilename(path) {
|
---|
32 | path = normalizePath(path);
|
---|
33 | const index = path.lastIndexOf('/');
|
---|
34 | return path.slice(0, index + 1);
|
---|
35 | }
|
---|
36 | /**
|
---|
37 | * Normalizes a protocol-relative URL, but keeps it protocol relative by
|
---|
38 | * stripping out the protocl before returning it.
|
---|
39 | */
|
---|
40 | function normalizeProtocolRelative(input, absoluteBase) {
|
---|
41 | const { href, protocol } = new Url(input, absoluteBase);
|
---|
42 | return href.slice(protocol.length);
|
---|
43 | }
|
---|
44 | /**
|
---|
45 | * Normalizes a simple path (one that has no ".."s, or is absolute so ".."s can
|
---|
46 | * be normalized absolutely).
|
---|
47 | */
|
---|
48 | function normalizeSimplePath(input) {
|
---|
49 | const { href } = new Url(input, 'https://foo.com/');
|
---|
50 | return href.slice('https://foo.com/'.length);
|
---|
51 | }
|
---|
52 | /**
|
---|
53 | * Normalizes a path, ensuring that excess ".."s are preserved for relative
|
---|
54 | * paths in the output.
|
---|
55 | *
|
---|
56 | * If the input is absolute, this will return an absolutey normalized path, but
|
---|
57 | * it will not have a leading "/".
|
---|
58 | *
|
---|
59 | * If the input has a leading "..", the output will have a leading "..".
|
---|
60 | *
|
---|
61 | * If the input has a leading ".", the output will not have a leading "."
|
---|
62 | * unless there are too many ".."s, in which case there will be a leading "..".
|
---|
63 | */
|
---|
64 | function normalizePath(input) {
|
---|
65 | // If there are no ".."s, we can treat this as if it were an absolute path.
|
---|
66 | // The return won't be an absolute path, so it's easy.
|
---|
67 | if (!parentRegex.test(input))
|
---|
68 | return normalizeSimplePath(input);
|
---|
69 | // We already found one "..". Let's see how many there are.
|
---|
70 | let total = 1;
|
---|
71 | while (parentRegex.test(input))
|
---|
72 | total++;
|
---|
73 | // If there are ".."s, we need to prefix the the path with the same number of
|
---|
74 | // unique directories. This is to ensure that we "remember" how many parent
|
---|
75 | // directories we are accessing. Eg, "../../.." must keep 3, and "foo/../.."
|
---|
76 | // must keep 1.
|
---|
77 | const uniqDirectory = `z${uniqInStr(input)}/`;
|
---|
78 | // uniqDirectory is just a "z", followed by numbers, followed by a "/". So
|
---|
79 | // generating a runtime regex from it is safe. We'll use this search regex to
|
---|
80 | // strip out our uniq directory names and insert any needed ".."s.
|
---|
81 | const search = new RegExp(`^(?:${uniqDirectory})*`);
|
---|
82 | // Now we can resolve the total path. If there are excess ".."s, they will
|
---|
83 | // eliminate one or more of the unique directories we prefix with.
|
---|
84 | const relative = normalizeSimplePath(uniqDirectory.repeat(total) + input);
|
---|
85 | // We can now count the number of unique directories that were eliminated. If
|
---|
86 | // there were 3, and 1 was eliminated, we know we only need to add 1 "..". If
|
---|
87 | // 2 were eliminated, we need to insert 2 ".."s. If all 3 were eliminated,
|
---|
88 | // then we need 3, etc. This replace is guranteed to match (it may match 0 or
|
---|
89 | // more times), and we can count the total match to see how many were eliminated.
|
---|
90 | return relative.replace(search, (all) => {
|
---|
91 | const leftover = all.length / uniqDirectory.length;
|
---|
92 | return '../'.repeat(total - leftover);
|
---|
93 | });
|
---|
94 | }
|
---|
95 | /**
|
---|
96 | * Attempts to resolve `input` URL relative to `base`.
|
---|
97 | */
|
---|
98 | function resolve(input, base) {
|
---|
99 | if (!base)
|
---|
100 | base = '';
|
---|
101 | // Absolute URLs are very easy to resolve right.
|
---|
102 | if (isAbsoluteUrl(input))
|
---|
103 | return new Url(input).href;
|
---|
104 | if (base) {
|
---|
105 | // Absolute URLs are easy...
|
---|
106 | if (isAbsoluteUrl(base))
|
---|
107 | return new Url(input, base).href;
|
---|
108 | // If base is protocol relative, we'll resolve with it but keep the result
|
---|
109 | // protocol relative.
|
---|
110 | if (base.startsWith('//'))
|
---|
111 | return normalizeProtocolRelative(input, `https:${base}`);
|
---|
112 | }
|
---|
113 | // Normalize input, but keep it protocol relative. We know base doesn't supply
|
---|
114 | // a protocol, because that would have been handled above.
|
---|
115 | if (input.startsWith('//'))
|
---|
116 | return normalizeProtocolRelative(input, 'https://foo.com/');
|
---|
117 | // We now know that base (if there is one) and input are paths. We've handled
|
---|
118 | // both absolute and protocol-relative variations above.
|
---|
119 | // Absolute paths don't need any special handling, because they cannot have
|
---|
120 | // extra "." or ".."s. That'll all be stripped away. Input takes priority here,
|
---|
121 | // because if input is an absolute path, base path won't affect it in any way.
|
---|
122 | if (input.startsWith('/'))
|
---|
123 | return '/' + normalizeSimplePath(input);
|
---|
124 | // Since input and base are paths, we need to join them to do any further
|
---|
125 | // processing. Paths are joined at the directory level, so we need to remove
|
---|
126 | // the base's filename before joining. We also know that input does not have a
|
---|
127 | // leading slash, and that the stripped base will have a trailing slash if
|
---|
128 | // there are any directories (or it'll be empty).
|
---|
129 | const joined = stripPathFilename(base) + input;
|
---|
130 | // If base is an absolute path, then input will be relative to it.
|
---|
131 | if (base.startsWith('/'))
|
---|
132 | return '/' + normalizeSimplePath(joined);
|
---|
133 | // We now know both base (if there is one) and input are relative paths.
|
---|
134 | const relative = normalizePath(joined);
|
---|
135 | // If base started with a leading ".", or there is no base and input started
|
---|
136 | // with a ".", then we need to ensure that the relative path starts with a
|
---|
137 | // ".". We don't know if relative starts with a "..", though, so check before
|
---|
138 | // prepending.
|
---|
139 | if ((base || input).startsWith('.') && !relative.startsWith('.')) {
|
---|
140 | return './' + relative;
|
---|
141 | }
|
---|
142 | return relative;
|
---|
143 | }
|
---|
144 |
|
---|
145 | export default resolve;
|
---|
146 | //# sourceMappingURL=resolve-uri.mjs.map
|
---|