]> Lady’s Gitweb - Pisces/blob - iri.js
Make base32 handling less forgiving
[Pisces] / iri.js
1 // ♓🌟 Piscēs ∷ iri.js
2 // ====================================================================
3 //
4 // Copyright © 2020, 2022 Lady [@ Lady’s Computer].
5 //
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
9
10 import { push, splice } from "./collection.js";
11 import { bind, call } from "./function.js";
12 import { objectCreate } from "./object.js";
13 import {
14 asciiUppercase,
15 getFirstSubstringIndex,
16 getLastSubstringIndex,
17 join,
18 Matcher,
19 rawString,
20 stringStartsWith,
21 substring,
22 } from "./string.js";
23
24 const sub·delims = rawString`[!\$&'()*+,;=]`;
25 const gen·delims = rawString`[:/?#\[\]@]`;
26 //deno-lint-ignore no-unused-vars
27 const reserved = rawString`${gen·delims}|${sub·delims}`;
28 const unreserved = rawString`[A-Za-z0-9\-\._~]`;
29 const pct·encoded = rawString`%[0-9A-Fa-f][0-9A-Fa-f]`;
30 const dec·octet =
31 rawString`[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]`;
32 const IPv4address =
33 rawString`(?:${dec·octet})\.(?:${dec·octet})\.(?:${dec·octet})\.(?:${dec·octet})`;
34 const h16 = rawString`[0-9A-Fa-f]{1,4}`;
35 const ls32 = rawString`(?:${h16}):(?:${h16})|${IPv4address}`;
36 const IPv6address =
37 rawString`(?:(?:${h16}):){6}(?:${ls32})|::(?:(?:${h16}):){5}(?:${ls32})|(?:${h16})?::(?:(?:${h16}):){4}(?:${ls32})|(?:(?:(?:${h16}):){0,1}(?:${h16}))?::(?:(?:${h16}):){3}(?:${ls32})|(?:(?:(?:${h16}):){0,2}(?:${h16}))?::(?:(?:${h16}):){2}(?:${ls32})|(?:(?:(?:${h16}):){0,3}(?:${h16}))?::(?:${h16}):(?:${ls32})|(?:(?:(?:${h16}):){0,4}(?:${h16}))?::(?:${ls32})|(?:(?:(?:${h16}):){0,5}(?:${h16}))?::(?:${h16})|(?:(?:(?:${h16}):){0,6}(?:${h16}))?::`;
38 const IPvFuture =
39 rawString`v[0-9A-Fa-f]{1,}\.(?:${unreserved}|${sub·delims}|:)`;
40 const IP·literal = rawString`\[(?:${IPv6address}|${IPvFuture})\]`;
41 const port = rawString`[0-9]*`;
42 const scheme = rawString`[A-Za-z][A-Za-z0-9+\-\.]*`;
43 const pchar =
44 rawString`${unreserved}|${pct·encoded}|${sub·delims}|[:@]`;
45 const fragment = rawString`(?:${pchar}|[/?])*`;
46 const query = rawString`(?:${pchar}|[/?])*`;
47 const segment·nz·nc =
48 rawString`(?:${unreserved}|${pct·encoded}|${sub·delims}|@)+`;
49 const segment·nz = rawString`(?:${pchar})+`;
50 const segment = rawString`(?:${pchar})*`;
51 const path·empty = rawString``;
52 const path·rootless = rawString`(?:${segment·nz})(?:/(?:${segment}))*`;
53 const path·noscheme =
54 rawString`(?:${segment·nz·nc})(?:/(?:${segment}))*`;
55 const path·absolute =
56 rawString`/(?:(?:${segment·nz})(?:/(?:${segment}))*)?`;
57 const path·abempty = rawString`(?:/(?:${segment}))*`;
58 const path =
59 rawString`${path·abempty}|${path·absolute}|${path·noscheme}|${path·rootless}|${path·empty}`;
60 const reg·name =
61 rawString`(?:${unreserved}|${pct·encoded}|${sub·delims})*`;
62 const host = rawString`${IP·literal}|${IPv4address}|${reg·name}`;
63 const userinfo =
64 rawString`(?:${unreserved}|${pct·encoded}|${sub·delims}|:)*`;
65 const authority =
66 rawString`(?:(?:${userinfo})@)?(?:${host})(?::(?:${port}))?`;
67 const relative·part =
68 rawString`//(?:${authority})(?:${path·abempty})|(?:${path·absolute})|(?:${path·noscheme})|(?:${path·empty})`;
69 const relative·ref =
70 rawString`(?:${relative·part})(?:\?(?:${query}))?(?:#(?:${fragment}))?`;
71 const hier·part =
72 rawString`//(?:${authority})(?:${path·abempty})|(?:${path·absolute})|(?:${path·rootless})|(?:${path·empty})`;
73 const absolute·URI =
74 rawString`(?:${scheme}):(?:${hier·part})(?:\?(?:${query}))?`;
75 const URI =
76 rawString`(?:${scheme}):(?:${hier·part})(?:\?(?:${query}))?(?:#(?:${fragment}))?`;
77 const URI·reference = rawString`(?:${URI})|(?:${relative·ref})`;
78
79 const iprivate =
80 rawString`[\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]`;
81 const ucschar =
82 rawString`[\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E0000}-\u{EFFFD}]`;
83 const iunreserved = rawString`[A-Za-z0-9\-\._~]|${ucschar}`;
84 const ipchar =
85 rawString`${iunreserved}|${pct·encoded}|${sub·delims}|[:@]`;
86 const ifragment = rawString`(?:${ipchar}|[/?])*`;
87 const iquery = rawString`(?:${ipchar}|${iprivate}|[/?])*`;
88 const isegment·nz·nc =
89 rawString`(?:${iunreserved}|${pct·encoded}|${sub·delims}|@)+`;
90 const isegment·nz = rawString`(?:${ipchar})+`;
91 const isegment = rawString`(?:${ipchar})*`;
92 const ipath·empty = rawString``;
93 const ipath·rootless =
94 rawString`(?:${isegment·nz})(?:/(?:${isegment}))*`;
95 const ipath·noscheme =
96 rawString`(?:${isegment·nz·nc})(?:/(?:${isegment}))*`;
97 const ipath·absolute =
98 rawString`/(?:(?:${isegment·nz})(?:/(?:${isegment}))*)?`;
99 const ipath·abempty = rawString`(?:/(?:${isegment}))*`;
100 const ipath =
101 rawString`${ipath·abempty}|${ipath·absolute}|${ipath·noscheme}|${ipath·rootless}|${ipath·empty}`;
102 const ireg·name =
103 rawString`(?:${iunreserved}|${pct·encoded}|${sub·delims})*`;
104 const ihost = rawString`${IP·literal}|${IPv4address}|${ireg·name}`;
105 const iuserinfo =
106 rawString`(?:${iunreserved}|${pct·encoded}|${sub·delims}|:)*`;
107 const iauthority =
108 rawString`(?:(?:${iuserinfo})@)?(?:${ihost})(?::(?:${port}))?`;
109 const irelative·part =
110 rawString`//(?:${iauthority})(?:${ipath·abempty})|(?:${ipath·absolute})|(?:${ipath·noscheme})|(?:${ipath·empty})`;
111 const irelative·ref =
112 rawString`(?:${irelative·part})(?:\?(?:${iquery}))?(?:#(?:${ifragment}))?`;
113 const ihier·part =
114 rawString`//(?:${iauthority})(?:${ipath·abempty})|(?:${ipath·absolute})|(?:${ipath·rootless})|(?:${ipath·empty})`;
115 const absolute·IRI =
116 rawString`(?:${scheme}):(?:${ihier·part})(?:\?(?:${iquery}))?`;
117 const IRI =
118 rawString`(?:${scheme}):(?:${ihier·part})(?:\?(?:${iquery}))?(?:#(?:${ifragment}))?`;
119 const IRI·reference = rawString`(?:${IRI})|(?:${irelative·ref})`;
120
121 const leiri·iprivate =
122 rawString`[\u{E000}-\u{F8FF}\u{E0000}-\u{E0FFF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]`;
123 const leiri·ucschar =
124 rawString`[ <>"{}|\\^${"\x60"}\u{0}-\u{1F}\u{7F}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}]`;
125 const leiri·iunreserved =
126 rawString`[A-Za-z0-9\-\._~]|${leiri·ucschar}`;
127 const leiri·ipchar =
128 rawString`${leiri·iunreserved}|${pct·encoded}|${sub·delims}|[:@]`;
129 const leiri·ifragment = rawString`(?:${leiri·ipchar}|[/?])*`;
130 const leiri·iquery =
131 rawString`(?:${leiri·ipchar}|${leiri·iprivate}|[/?])*`;
132 const leiri·isegment·nz·nc =
133 rawString`(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims}|@)+`;
134 const leiri·isegment·nz = rawString`(?:${leiri·ipchar})+`;
135 const leiri·isegment = rawString`(?:${leiri·ipchar})*`;
136 const leiri·ipath·empty = rawString``;
137 const leiri·ipath·rootless =
138 rawString`(?:${leiri·isegment·nz})(?:/(?:${leiri·isegment}))*`;
139 const leiri·ipath·noscheme =
140 rawString`(?:${leiri·isegment·nz·nc})(?:/(?:${leiri·isegment}))*`;
141 const leiri·ipath·absolute =
142 rawString`/(?:(?:${leiri·isegment·nz})(?:/(?:${leiri·isegment}))*)?`;
143 const leiri·ipath·abempty = rawString`(?:/(?:${leiri·isegment}))*`;
144 const leiri·ipath =
145 rawString`${leiri·ipath·abempty}|${leiri·ipath·absolute}|${leiri·ipath·noscheme}|${leiri·ipath·rootless}|${leiri·ipath·empty}`;
146 const leiri·ireg·name =
147 rawString`(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims})*`;
148 const leiri·ihost =
149 rawString`${IP·literal}|${IPv4address}|${leiri·ireg·name}`;
150 const leiri·iuserinfo =
151 rawString`(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims}|:)*`;
152 const leiri·iauthority =
153 rawString`(?:(?:${leiri·iuserinfo})@)?(?:${leiri·ihost})(?::(?:${port}))?`;
154 const leiri·irelative·part =
155 rawString`//(?:${leiri·iauthority})(?:${leiri·ipath·abempty})|(?:${leiri·ipath·absolute})|(?:${leiri·ipath·noscheme})|(?:${leiri·ipath·empty})`;
156 const leiri·irelative·ref =
157 rawString`(?:${leiri·irelative·part})(?:\?(?:${leiri·iquery}))?(?:#(?:${leiri·ifragment}))?`;
158 const leiri·ihier·part =
159 rawString`//(?:${leiri·iauthority})(?:${leiri·ipath·abempty})|(?:${leiri·ipath·absolute})|(?:${leiri·ipath·rootless})|(?:${leiri·ipath·empty})`;
160 const absolute·LEIRI =
161 rawString`(?:${scheme}):(?:${leiri·ihier·part})(?:\?(?:${leiri·iquery}))?`;
162 const LEIRI =
163 rawString`(?:${scheme}):(?:${leiri·ihier·part})(?:\?(?:${leiri·iquery}))?(?:#(?:${leiri·ifragment}))?`;
164 const LEIRI·reference =
165 rawString`(?:${LEIRI})|(?:${leiri·irelative·ref})`;
166
167 export const {
168 /**
169 * Recomposes an (L·E·)I·R·I reference from its component parts.
170 *
171 * See §5.3 of R·F·C 3986.
172 */
173 composeReference,
174
175 /**
176 * Converts an L·E·I·R·I to the corresponding I·R·I by
177 * percent‐encoding unsupported characters.
178 *
179 * This function is somewhat complex because the I·R·I syntax allows
180 * private·use characters *only* in the query.
181 */
182 escapeForIRI,
183
184 /**
185 * Converts an (L·E·)I·R·I to the corresponding U·R·I by
186 * percent‐encoding unsupported characters.
187 *
188 * This does not punycode the authority.
189 */
190 escapeForURI,
191
192 /**
193 * Removes all dot segments ("." or "..") from the provided
194 * (L·E·)I·R·I.
195 *
196 * See §5.2.4 of R·F·C 3986.
197 */
198 removeDotSegments,
199 } = (() => {
200 const TE = TextEncoder;
201 const { iterator: iteratorSymbol } = Symbol;
202 const { toString: numberToString } = Number.prototype;
203 const { encode: teEncode } = TE.prototype;
204
205 const { [iteratorSymbol]: arrayIterator } = Array.prototype;
206 const {
207 next: arrayIteratorNext,
208 } = Object.getPrototypeOf([][iteratorSymbol]());
209 const {
210 next: generatorIteratorNext,
211 } = Object.getPrototypeOf(function* () {}.prototype);
212 const { [iteratorSymbol]: stringIterator } = String.prototype;
213 const {
214 next: stringIteratorNext,
215 } = Object.getPrototypeOf(""[iteratorSymbol]());
216
217 const iriCharacterIterablePrototype = {
218 [iteratorSymbol]() {
219 return {
220 next: bind(
221 stringIteratorNext,
222 call(stringIterator, this.source, []),
223 [],
224 ),
225 };
226 },
227 };
228 const iriGeneratorIterablePrototype = {
229 [iteratorSymbol]() {
230 return {
231 next: bind(generatorIteratorNext, this.generator(), []),
232 };
233 },
234 };
235 const iriSegmentIterablePrototype = {
236 [iteratorSymbol]() {
237 return {
238 next: bind(
239 arrayIteratorNext,
240 call(arrayIterator, this.segments, []),
241 [],
242 ),
243 };
244 },
245 };
246
247 return {
248 composeReference: ($) =>
249 join(
250 objectCreate(
251 iriGeneratorIterablePrototype,
252 {
253 generator: {
254 value: function* () {
255 const { scheme, authority, path, query, fragment } = $;
256 if (scheme != null) {
257 // A scheme is present.
258 yield scheme;
259 yield ":";
260 } else {
261 // No scheme is present.
262 /* do nothing */
263 }
264 if (authority != null) {
265 // An authority is present.
266 yield "//";
267 yield authority;
268 } else {
269 // No authority is present.
270 /* do nothing */
271 }
272 yield path ?? "";
273 if (query != null) {
274 // A query is present.
275 yield "?";
276 yield query;
277 } else {
278 // No query is present.
279 /* do nothing */
280 }
281 if (fragment != null) {
282 // A fragment is present.
283 yield "#";
284 yield fragment;
285 } else {
286 // No fragment is present.
287 /* do nothing */
288 }
289 },
290 },
291 },
292 ),
293 "",
294 ),
295 escapeForIRI: ($) => {
296 const components = parseReference($);
297
298 // The path will always be present (although perhaps empty) on a
299 // successful parse. If it isn’t (and parsing failed), treat the
300 // entire input as the path.
301 components.path ??= `${$}`;
302
303 // Escape disallowed codepoints in each component and compose an
304 // I·R·I from the result.
305 const reference = objectCreate(null);
306 for (const componentName in components) {
307 const componentValue = components[componentName];
308 reference[componentName] = componentValue == null
309 ? undefined
310 : join(
311 objectCreate(
312 iriGeneratorIterablePrototype,
313 {
314 generator: {
315 value: function* () {
316 const encoder = new TE();
317 for (
318 const character of objectCreate(
319 iriCharacterIterablePrototype,
320 { source: { value: componentValue } },
321 )
322 ) {
323 if (
324 new Matcher(
325 `${leiri·ucschar}|${leiri·iprivate}`,
326 )(character) &&
327 !new Matcher(
328 `${ucschar}${
329 componentName == "query"
330 ? `|${iprivate}`
331 : ""
332 }`,
333 )(character)
334 ) {
335 // This codepoint needs to be escaped.
336 const encoded = call(teEncode, encoder, [
337 character,
338 ]);
339 for (
340 let index = 0;
341 index < encoded.length;
342 ++index
343 ) {
344 const byte = encoded[index];
345 yield `%${byte < 0x10 ? "0" : ""}${
346 asciiUppercase(
347 call(numberToString, byte, [0x10]),
348 )
349 }`;
350 }
351 } else {
352 // This codepoint does not need escaping.
353 yield character;
354 }
355 }
356 },
357 },
358 },
359 ),
360 "",
361 );
362 }
363 return composeReference(reference);
364 },
365 escapeForURI: ($) =>
366 join(
367 objectCreate(
368 iriGeneratorIterablePrototype,
369 {
370 generator: {
371 value: function* () {
372 const encoder = new TE();
373 for (
374 const character of objectCreate(
375 iriCharacterIterablePrototype,
376 { source: { value: `${$}` } },
377 )
378 ) {
379 if (
380 new Matcher(
381 `${leiri·ucschar}|${leiri·iprivate}`,
382 )(character)
383 ) {
384 // This codepoint needs to be escaped.
385 const encoded = call(teEncode, encoder, [
386 character,
387 ]);
388 for (
389 let index = 0;
390 index < encoded.length;
391 ++index
392 ) {
393 const byte = encoded[index];
394 yield `%${byte < 0x10 ? "0" : ""}${
395 asciiUppercase(
396 call(numberToString, byte, [0x10]),
397 )
398 }`;
399 }
400 } else {
401 // This codepoint does not need escaping.
402 yield character;
403 }
404 }
405 },
406 },
407 },
408 ),
409 "",
410 ),
411 removeDotSegments: ($) => {
412 const input = `${$}`;
413 const output = [];
414 const { length } = input;
415 let index = 0;
416 while (index < length) {
417 if (stringStartsWith(input, "../", index)) {
418 // The input starts with a double leader; drop it. This can
419 // only occur at the beginning of the input.
420 index += 3;
421 } else if (stringStartsWith(input, "./", index)) {
422 // The input starts with a single leader; drop it. This can
423 // only occur at the beginning of the input.
424 index += 2;
425 } else if (stringStartsWith(input, "/./", index)) {
426 // The input starts with a slash, single leader, and another
427 // slash. Ignore it, and move the input to just before the
428 // second slash.
429 index += 2;
430 } else if (
431 stringStartsWith(input, "/.", index) && index + 2 == length
432 ) {
433 // The input starts with a slash and single leader, and this
434 // exhausts the string. Push an empty segment and move the
435 // index to the end of the string.
436 push(output, "/");
437 index = length;
438 } else if (stringStartsWith(input, "/../", index)) {
439 // The input starts with a slash, double leader, and another
440 // slash. Drop a segment from the output, and move the input
441 // to just before the second slash.
442 index += 3;
443 splice(output, -1, 1);
444 } else if (
445 stringStartsWith(input, "/..", index) && index + 3 == length
446 ) {
447 // The input starts with a slash and single leader, and this
448 // exhausts the string. Drop a segment from the output, push
449 // an empty segment, and move the index to the end of the
450 // string.
451 splice(output, -1, 1, "/");
452 index = length;
453 } else if (
454 stringStartsWith(input, ".", index) && index + 1 == length ||
455 stringStartsWith(input, "..", index) && index + 2 == length
456 ) {
457 // The input starts with a single or double leader, and this
458 // exhausts the string. Do nothing (this can only occur at
459 // the beginning of input) and move the index to the end of
460 // the string.
461 index = length;
462 } else {
463 // The input does not start with a leader. Advance the index
464 // to the position before the next slash and push the segment
465 // between the old and new positions.
466 const nextIndex = getFirstSubstringIndex(
467 input,
468 "/",
469 index + 1,
470 );
471 if (nextIndex == -1) {
472 // No slash remains; set index to the end of the string.
473 push(output, substring(input, index));
474 index = length;
475 } else {
476 // There are further path segments.
477 push(output, substring(input, index, nextIndex));
478 index = nextIndex;
479 }
480 }
481 }
482 return join(
483 objectCreate(
484 iriSegmentIterablePrototype,
485 { segments: { value: output } },
486 ),
487 "",
488 );
489 },
490 };
491 })();
492
493 export const {
494 isAbsoluteIRI, // I·R·I with no fragment
495 isAbsoluteLEIRI, // L·E·I·R·I with no fragment
496 isAbsoluteURI, // U·R·I with no fragment
497 isIRI,
498 isIRIPath,
499 isIRIReference,
500 isIRISuffix, // only authority, path, query, fragment
501 isLEIRI,
502 isLEIRIPath,
503 isLEIRIReference,
504 isLEIRISuffix, // only authority, path, query, fragment
505 isURI,
506 isURIPath,
507 isURIReference,
508 isURISuffix, // only authority, path, query, fragment
509 } = Object.fromEntries(
510 Object.entries({
511 isAbsoluteIRI: absolute·IRI,
512 isAbsoluteLEIRI: absolute·LEIRI,
513 isAbsoluteURI: absolute·URI,
514 isIRI: IRI,
515 isIRIPath: ipath,
516 isIRIReference: IRI·reference,
517 isIRISuffix:
518 rawString`(?:${iauthority})(?:${ipath·abempty})(?:\?(?:${iquery}))?(?:#(?:${ifragment}))?`,
519 isLEIRI: LEIRI,
520 isLEIRIPath: leiri·ipath,
521 isLEIRIReference: LEIRI·reference,
522 isLEIRISuffix:
523 rawString`(?:${leiri·iauthority})(?:${leiri·ipath·abempty})(?:\?(?:${leiri·iquery}))?(?:#(?:${leiri·ifragment}))?`,
524 isURI: URI,
525 isURIPath: path,
526 isURIReference: URI·reference,
527 isURISuffix:
528 rawString`(?:${authority})(?:${path·abempty})(?:\?(?:${query}))?(?:#(?:${fragment}))?`,
529 }).map(
530 ([key, value]) => [key, new Matcher(rawString`^(?:${value})$`)],
531 ),
532 );
533
534 /**
535 * Returns the result of merging the provided reference path with the
536 * provided base path.
537 *
538 * See §5.2.3 of R·F·C 3986.
539 */
540 export const mergePaths = (base, reference) => {
541 const baseStr = `${base}` || "/";
542 return `${
543 substring(baseStr, 0, getLastSubstringIndex(baseStr, "/") + 1)
544 }${reference}`;
545 };
546
547 export const {
548 /**
549 * Returns the `scheme`, `authority`, `path`, `query`, and `fragment`
550 * of the provided (L·E·)I·R·I reference.
551 *
552 * `path` will always be defined for valid references, and will be
553 * undefined for values which are not valid L·E·I·R·Is.
554 */
555 parseReference,
556 } = (() => {
557 const RE = RegExp;
558 const { prototype: rePrototype } = RE;
559 const { exec: reExec } = rePrototype;
560 return {
561 parseReference: ($) => {
562 const re = new RE(
563 rawString`^(?:(?<absolute·scheme>${scheme}):(?://(?<absolute·authority>${leiri·iauthority})(?<absolute·patha>${leiri·ipath·abempty})|(?<absolute·pathb>(?:${leiri·ipath·absolute})|(?:${leiri·ipath·rootless})|(?:${leiri·ipath·empty})))(?:\?(?<absolute·query>${leiri·iquery}))?(?:#(?<absolute·fragment>${leiri·ifragment}))?|(?://(?<relative·authority>${leiri·iauthority})(?<relative·patha>${leiri·ipath·abempty})|(?<relative·pathb>(?:${leiri·ipath·absolute})|(?:${leiri·ipath·noscheme})|(?:${leiri·ipath·empty})))(?:\?(?<relative·query>${leiri·iquery}))?(?:#(?<relative·fragment>${leiri·ifragment}))?)$`,
564 "u",
565 );
566 const {
567 absolute·scheme,
568 absolute·authority,
569 absolute·patha,
570 absolute·pathb,
571 absolute·query,
572 absolute·fragment,
573 relative·authority,
574 relative·patha,
575 relative·pathb,
576 relative·query,
577 relative·fragment,
578 } = call(reExec, re, [$])?.groups ?? {};
579 return {
580 scheme: absolute·scheme,
581 authority: absolute·authority ?? relative·authority,
582 path: absolute·patha ?? absolute·pathb ?? relative·patha ??
583 relative·pathb,
584 query: absolute·query ?? relative·query,
585 fragment: absolute·fragment ?? relative·fragment,
586 };
587 },
588 };
589 })();
590
591 /**
592 * Resolves the provided reference relative to the provided base
593 * (L·E·)I·R·I.
594 *
595 * See §5.2 of R·F·C 3986.
596 */
597 export const resolveReference = (R, Base = location ?? "") => {
598 const {
599 scheme: Base·scheme,
600 authority: Base·authority,
601 path: Base·path,
602 query: Base·query,
603 } = parseReference(Base);
604 if (Base·scheme == null) {
605 // Base I·R·I’s must be valid I·R·I’s, meaning they must have a
606 // scheme.
607 throw new TypeError(
608 `Piscēs: Base did not have a scheme: ${Base}.`,
609 );
610 } else {
611 // The provided Base I·R·I is valid.
612 const {
613 scheme: R·scheme,
614 authority: R·authority,
615 path: R·path,
616 query: R·query,
617 fragment: R·fragment,
618 } = parseReference(R);
619 return composeReference(
620 R·scheme != null
621 ? {
622 scheme: R·scheme,
623 authority: R·authority,
624 path: removeDotSegments(R·path),
625 query: R·query,
626 fragment: R·fragment,
627 }
628 : R·authority != null
629 ? {
630 scheme: Base·scheme,
631 authority: R·authority,
632 path: removeDotSegments(R·path),
633 query: R·query,
634 fragment: R·fragment,
635 }
636 : !R·path
637 ? {
638 scheme: Base·scheme,
639 authority: Base·authority,
640 path: Base·path,
641 query: R·query ?? Base·query,
642 fragment: R·fragment,
643 }
644 : {
645 scheme: Base·scheme,
646 authority: Base·authority,
647 path: R·path[0] == "/"
648 ? removeDotSegments(R·path)
649 : removeDotSegments(mergePaths(Base·path, R·path)),
650 query: R·query,
651 fragment: R·fragment,
652 },
653 );
654 }
655 };
This page took 0.370127 seconds and 5 git commands to generate.