[6a3a178] | 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
|
---|