From: Lady Date: Tue, 17 May 2022 03:57:40 +0000 (-0700) Subject: More comprehensive support for RFC 3986 & RFC 3987 X-Git-Tag: 0.1.0~12 X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/edbef3a179c4404eb02a83860a76c1f11b9b63d2 More comprehensive support for RFC 3986 & RFC 3987 --- diff --git a/iri.js b/iri.js new file mode 100644 index 0000000..f1abe78 --- /dev/null +++ b/iri.js @@ -0,0 +1,372 @@ +// ♓🌟 Piscēs ∷ iri.js +// ==================================================================== +// +// Copyright © 2020, 2022 Lady [@ Lady’s Computer]. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at . + +const sub·delims = String.raw`[!\$&'()*+,;=]`; +const gen·delims = String.raw`[:/?#\[\]@]`; +//deno-lint-ignore no-unused-vars +const reserved = String.raw`${gen·delims}|${sub·delims}`; +const unreserved = String.raw`[A-Za-z0-9\-\._~]`; +const pct·encoded = String.raw`%[0-9A-Fa-f][0-9A-Fa-f]`; +const dec·octet = String.raw + `[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]`; +const IPv4address = String.raw + `(?:${dec·octet})\.(?:${dec·octet})\.(?:${dec·octet})\.(?:${dec·octet})`; +const h16 = String.raw`[0-9A-Fa-f]{1,4}`; +const ls32 = String.raw`(?:${h16}):(?:${h16})|${IPv4address}`; +const IPv6address = String.raw + `(?:(?:${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}))?::`; +const IPvFuture = String.raw + `v[0-9A-Fa-f]{1,}\.(?:${unreserved}|${sub·delims}|:)`; +const IP·literal = String.raw`\[(?:${IPv6address}|${IPvFuture})\]`; +const port = String.raw`[0-9]*`; +const scheme = String.raw`[A-Za-z][A-Za-z0-9+\-\.]*`; +const pchar = String.raw + `${unreserved}|${pct·encoded}|${sub·delims}|[:@]`; +const fragment = String.raw`(?:${pchar}|[/?])*`; +const query = String.raw`(?:${pchar}|[/?])*`; +const segment·nz·nc = String.raw + `(?:${unreserved}|${pct·encoded}|${sub·delims}|@)+`; +const segment·nz = String.raw`(?:${pchar})+`; +const segment = String.raw`(?:${pchar})*`; +const path·empty = String.raw``; +const path·rootless = String.raw + `(?:${segment·nz})(?:/(?:${segment}))*`; +const path·noscheme = String.raw + `(?:${segment·nz·nc})(?:/(?:${segment}))*`; +const path·absolute = String.raw + `/(?:(?:${segment·nz})(?:/(?:${segment}))*)?`; +const path·abempty = String.raw`(?:/(?:${segment}))*`; +//deno-lint-ignore no-unused-vars +const path = String.raw + `${path·abempty}|${path·absolute}|${path·noscheme}|${path·rootless}|${path·empty}`; +const reg·name = String.raw + `(?:${unreserved}|${pct·encoded}|${sub·delims})*`; +const host = String.raw`${IP·literal}|${IPv4address}|${reg·name}`; +const userinfo = String.raw + `(?:${unreserved}|${pct·encoded}|${sub·delims}|:)*`; +const authority = String.raw + `(?:(?:${userinfo})@)?(?:${host})(?::(?:${port}))?`; +const relative·part = String.raw + `//(?:${authority})(?:${path·abempty})|(?:${path·absolute})|(?:${path·noscheme})|(?:${path·empty})`; +const relative·ref = String.raw + `(?:${relative·part})(?:\?(?:${query}))?(?:#(?:${fragment}))?`; +const hier·part = String.raw + `//(?:${authority})(?:${path·abempty})|(?:${path·absolute})|(?:${path·rootless})|(?:${path·empty})`; +const absolute·URI = String.raw + `(?:${scheme}):(?:${hier·part})(?:\?(?:${query}))?`; +const URI = String.raw + `(?:${scheme}):(?:${hier·part})(?:\?(?:${query}))?(?:#(?:${fragment}))?`; +const URI·reference = String.raw`(?:${URI})|(?:${relative·ref})`; + +const iprivate = String.raw + `[\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]`; +const ucschar = String.raw + `[\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}]`; +const iunreserved = String.raw`[A-Za-z0-9\-\._~]|${ucschar}`; +const ipchar = String.raw + `${iunreserved}|${pct·encoded}|${sub·delims}|[:@]`; +const ifragment = String.raw`(?:${ipchar}|[/?])*`; +const iquery = String.raw`(?:${ipchar}|${iprivate}|[/?])*`; +const isegment·nz·nc = String.raw + `(?:${iunreserved}|${pct·encoded}|${sub·delims}|@)+`; +const isegment·nz = String.raw`(?:${ipchar})+`; +const isegment = String.raw`(?:${ipchar})*`; +const ipath·empty = String.raw``; +const ipath·rootless = String.raw + `(?:${isegment·nz})(?:/(?:${isegment}))*`; +const ipath·noscheme = String.raw + `(?:${isegment·nz·nc})(?:/(?:${isegment}))*`; +const ipath·absolute = String.raw + `/(?:(?:${isegment·nz})(?:/(?:${isegment}))*)?`; +const ipath·abempty = String.raw`(?:/(?:${isegment}))*`; +//deno-lint-ignore no-unused-vars +const ipath = String.raw + `${ipath·abempty}|${ipath·absolute}|${ipath·noscheme}|${ipath·rootless}|${ipath·empty}`; +const ireg·name = String.raw + `(?:${iunreserved}|${pct·encoded}|${sub·delims})*`; +const ihost = String.raw`${IP·literal}|${IPv4address}|${ireg·name}`; +const iuserinfo = String.raw + `(?:${iunreserved}|${pct·encoded}|${sub·delims}|:)*`; +const iauthority = String.raw + `(?:(?:${iuserinfo})@)?(?:${ihost})(?::(?:${port}))?`; +const irelative·part = String.raw + `//(?:${iauthority})(?:${ipath·abempty})|(?:${ipath·absolute})|(?:${ipath·noscheme})|(?:${ipath·empty})`; +const irelative·ref = String.raw + `(?:${irelative·part})(?:\?(?:${iquery}))?(?:#(?:${ifragment}))?`; +const ihier·part = String.raw + `//(?:${iauthority})(?:${ipath·abempty})|(?:${ipath·absolute})|(?:${ipath·rootless})|(?:${ipath·empty})`; +const absolute·IRI = String.raw + `(?:${scheme}):(?:${ihier·part})(?:\?(?:${iquery}))?`; +const IRI = String.raw + `(?:${scheme}):(?:${ihier·part})(?:\?(?:${iquery}))?(?:#(?:${ifragment}))?`; +const IRI·reference = String.raw`(?:${IRI})|(?:${irelative·ref})`; + +export const { + isAbsoluteURI, // U·R·I with no fragment + isURI, + isURIReference, + isAbsoluteIRI, // I·R·I with no fragment + isIRI, + isIRIReference, +} = Object.fromEntries( + Object.entries({ + isAbsoluteIRI: absolute·IRI, + isAbsoluteURI: absolute·URI, + isIRI: IRI, + isIRIReference: IRI·reference, + isURI: URI, + isURIReference: URI·reference, + }).map(([key, value]) => { + const regExp = new RegExp(`^(?:${value})$`, "u"); + return [ + key, + Object.defineProperties( + ($) => typeof $ == "string" && regExp.test($), + { + name: { value: key }, + [Symbol.match]: { + configurable: true, + enumerable: false, + get: () => regExp[Symbol.match].bind(regExp), + set: undefined, + }, + }, + ), + ]; + }), +); + +/** + * Recomposes an I·R·I reference from its component parts. + * + * See §5.3 of R·F·C 3986. + */ +export const composeReference = ($) => { + const result = []; + const { scheme, authority, path, query, fragment } = $; + if (scheme != null) { + result.push(scheme, ":"); + } else { + /* do nothing */ + } + if (authority != null) { + result.push("//", authority); + } else { + /* do nothing */ + } + result.push(path ?? ""); + if (query != null) { + result.push("?", query); + } else { + /* do nothing */ + } + if (fragment != null) { + result.push("#", fragment); + } else { + /* do nothing */ + } + return result.join(""); +}; + +/** + * Converts an I·R·I to the corresponding U·R·I by percent‐encoding + * unsupported characters. + * + * This does not punycode the authority. + */ +export const iri2uri = ($) => + [...function* () { + const encoder = new TextEncoder(); + for (const character of $) { + if (new RegExp(`${ucschar}|${iprivate}`, "u").test(character)) { + for (const byte of encoder.encode(character)) { + yield `%${byte.toString(0x10).toUpperCase()}`; + } + } else { + yield character; + } + } + }()].join(""); + +/** + * Merges a reference path with a base path. + * + * See §5.2.3 of R·F·C 3986. + */ +export const mergePaths = (base, reference) => { + const baseStr = `${base}`; + return `${ + baseStr.substring(0, baseStr.lastIndexOf("/") + 1) + }${reference}`; +}; + +/** + * Returns the `scheme`, `authority`, `path`, `query`, and `fragment` + * of the provided I·R·I reference. + */ +export const parseReference = ($) => { + const regExp = new RegExp( + String.raw + `^(?:(?${scheme}):(?://(?${iauthority})(?${ipath·abempty})|(?(?:${ipath·absolute})|(?:${ipath·rootless})|(?:${ipath·empty})))(?:\?(?${iquery}))?(?:#(?${ifragment}))?|(?://(?${iauthority})(?${ipath·abempty})|(?(?:${ipath·absolute})|(?:${ipath·noscheme})|(?:${ipath·empty})))(?:\?(?${iquery}))?(?:#(?${ifragment}))?)$`, + "u", + ); + const { + absolute·scheme, + absolute·authority, + absolute·patha, + absolute·pathb, + absolute·query, + absolute·fragment, + relative·authority, + relative·patha, + relative·pathb, + relative·query, + relative·fragment, + } = regExp.exec($)?.groups ?? {}; + return { + scheme: absolute·scheme, + authority: absolute·authority ?? relative·authority, + path: absolute·patha ?? absolute·pathb ?? relative·patha ?? + relative·pathb, + query: absolute·query ?? relative·query, + fragment: absolute·fragment ?? relative·fragment, + }; +}; + +/** + * Removes all dot segments ("." or "..") from the provided I·R·I. + * + * See §5.2.4 of R·F·C 3986. + */ +export const removeDotSegments = ($) => { + const input = `${$}`; + const output = []; + const { length } = input; + let index = 0; + while (index < length) { + if (input.startsWith("../", index)) { + // The input starts with a double leader; drop it. This can only + // occur at the beginning of the input. + index += 3; + } else if (input.startsWith("./", index)) { + // The input starts with a single leader; drop it. This can only + // occur at the beginning of the input. + index += 2; + } else if (input.startsWith("/./", index)) { + // The input starts with a slash, single leader, and another + // slash. Ignore it, and move the input to just before the second + // slash. + index += 2; + } else if (input.startsWith("/.", index) && index + 2 == length) { + // The input starts with a slash and single leader, and this + // exhausts the string. Push an empty segment and move the index + // to the end of the string. + output.push("/"); + index = length; + } else if (input.startsWith("/../", index)) { + // The input starts with a slash, double leader, and another + // slash. Drop a segment from the output, and move the input to + // just before the second slash. + index += 3; + output.splice(-1, 1); + } else if (input.startsWith("/..", index) && index + 3 == length) { + // The input starts with a slash and single leader, and this + // exhausts the string. Drop a segment from the output, push an + // empty segment, and move the index to the end of the string. + output.splice(-1, 1, "/"); + index = length; + } else if ( + input.startsWith(".", index) && index + 1 == length || + input.startsWith("..", index) && index + 2 == length + ) { + // The input starts with a single or double leader, and this + // exhausts the string. Do nothing (this can only occur at the + // beginning of input) and move the index to the end of the + // string. + index = length; + } else { + // The input does not start with a leader. Advance the index to + // the position before the next slash and push the segment + // between the old and new positions. + const nextIndex = input.indexOf("/", index + 1); + if (nextIndex == -1) { + // No slash remains; set index to the end of the string. + output.push(input.substring(index)); + index = length; + } else { + // There are further path segments. + output.push(input.substring(index, nextIndex)); + index = nextIndex; + } + } + } + return output.join(""); +}; + +/** + * Resolves the provided reference relative to the provided base I·R·I. + * + * See §5.2 of R·F·C 3986. + */ +export const resolveReference = (R, Base = location ?? "") => { + const { + scheme: Base·scheme, + authority: Base·authority, + path: Base·path, + query: Base·query, + } = parseReference(Base); + if (Base·scheme == null) { + throw new TypeError( + `Piscēs: Base I·R·I did not have a scheme: ${Base}.`, + ); + } else { + const { + scheme: R·scheme, + authority: R·authority, + path: R·path, + query: R·query, + fragment: R·fragment, + } = parseReference(R); + return composeReference( + R·scheme != null + ? { + scheme: R·scheme, + authority: R·authority, + path: removeDotSegments(R·path), + query: R·query, + fragment: R·fragment, + } + : R·authority != null + ? { + scheme: Base·scheme, + authority: R·authority, + path: removeDotSegments(R·path), + query: R·query, + fragment: R·fragment, + } + : !R·path + ? { + scheme: Base·scheme, + authority: Base·authority, + path: Base·path, + query: R·query ?? Base·query, + fragment: R·fragment, + } + : { + scheme: Base·scheme, + authority: Base·authority, + path: R·path[0] == "/" + ? removeDotSegments(R·path) + : removeDotSegments(mergePaths(Base·path || "/", R·path)), + query: R·query, + fragment: R·fragment, + }, + ); + } +}; diff --git a/iri.test.js b/iri.test.js new file mode 100644 index 0000000..51749cd --- /dev/null +++ b/iri.test.js @@ -0,0 +1,333 @@ +// ♓🌟 Piscēs ∷ iri.test.js +// ==================================================================== +// +// Copyright © 2020–2022 Lady [@ Lady’s Computer]. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at . + +import { + composeReference, + iri2uri, + isAbsoluteIRI, + isAbsoluteURI, + isIRI, + isIRIReference, + isURI, + isURIReference, + parseReference, + removeDotSegments, + resolveReference, +} from "./iri.js"; +import { + assert, + assertEquals, + assertStrictEquals, +} from "./dev-deps.js"; + +const exampleURIs = { + "ftp://ftp.is.co.za/rfc/rfc1808.txt": { + scheme: "ftp", + authority: "ftp.is.co.za", + path: "/rfc/rfc1808.txt", + }, + "http://www.ietf.org/rfc/rfc2396.txt": { + scheme: "http", + authority: "www.ietf.org", + path: "/rfc/rfc2396.txt", + }, + "ldap://[2001:db8::7]/c=GB?objectClass?one": { + scheme: "ldap", + authority: "[2001:db8::7]", + path: "/c=GB", + query: "objectClass?one", + }, + "mailto:John.Doe@example.com": { + scheme: "mailto", + path: "John.Doe@example.com", + }, + "news:comp.infosystems.www.servers.unix": { + scheme: "news", + path: "comp.infosystems.www.servers.unix", + }, + "tel:+1-816-555-1212": { + scheme: "tel", + path: "+1-816-555-1212", + }, + "telnet://192.0.2.16:80/": { + scheme: "telnet", + authority: "192.0.2.16:80", + path: "/", + }, + "urn:oasis:names:specification:docbook:dtd:xml:4.1.2": { + scheme: "urn", + path: "oasis:names:specification:docbook:dtd:xml:4.1.2", + }, + "foo://example.com:8042/over/there?name=ferret#nose": { + scheme: "foo", + authority: "example.com:8042", + path: "/over/there", + query: "name=ferret", + fragment: "nose", + }, +}; + +const exampleURIReferences = { + ...exampleURIs, + "./this:that": { + path: "./this:that", + }, +}; + +const exampleIRIs = { + ...exampleURIs, + "http://ヒキワリ.ナットウ.ニホン": { + scheme: "http", + authority: "ヒキワリ.ナットウ.ニホン", + path: "", + }, + "http://JP納豆.例.jp/dir1/引き割り.html": { + scheme: "http", + authority: "JP納豆.例.jp", + path: "/dir1/引き割り.html", + }, +}; + +const exampleIRIReferences = { + ...exampleURIReferences, + ...exampleIRIs, +}; + +Deno.test({ + name: "Identifies U·R·Is.", + fn: () => { + for (const uri of Object.keys(exampleURIs)) { + assert(isURI(uri)); + } + }, +}); + +Deno.test({ + name: "Identifies absolute U·R·Is.", + fn: () => { + for (const [uri, { fragment }] of Object.entries(exampleURIs)) { + assertStrictEquals(isAbsoluteURI(uri), fragment == null); + } + }, +}); + +Deno.test({ + name: "Identifies U·R·I references.", + fn: () => { + for (const uri of Object.keys(exampleURIReferences)) { + assert(isURIReference(uri)); + } + }, +}); + +Deno.test({ + name: "Identifies I·R·Is.", + fn: () => { + for (const iri of Object.keys(exampleIRIs)) { + assert(isIRI(iri)); + } + }, +}); + +Deno.test({ + name: "Identifies absolute I·R·Is.", + fn: () => { + for (const [iri, { fragment }] of Object.entries(exampleIRIs)) { + assertStrictEquals(isAbsoluteIRI(iri), fragment == null); + } + }, +}); + +Deno.test({ + name: "Identifies I·R·I references.", + fn: () => { + for (const iri of Object.keys(exampleIRIReferences)) { + assert(isIRIReference(iri)); + } + }, +}); + +Deno.test({ + name: "Correctly parses references.", + fn: () => { + for (const [iri, value] of Object.entries(exampleIRIReferences)) { + assertEquals(parseReference(iri), { + scheme: undefined, + authority: undefined, + path: undefined, + query: undefined, + fragment: undefined, + ...value, + }); + } + }, +}); + +Deno.test({ + name: "Correctly composes references.", + fn: () => { + for (const [iri, value] of Object.entries(exampleIRIReferences)) { + assertStrictEquals(composeReference(value), iri); + } + }, +}); + +Deno.test({ + name: "Converts IRIs to URIs.", + fn: () => { + assertStrictEquals( + iri2uri("/dir1/引き割り.html"), + "/dir1/%E5%BC%95%E3%81%8D%E5%89%B2%E3%82%8A.html", + ); + }, +}); + +Deno.test({ + name: "Correctly removes dot segments.", + fn: () => { + assertStrictEquals(removeDotSegments("/a/b/c/./../../g"), "/a/g"); + assertStrictEquals( + removeDotSegments("mid/content=5/../6"), + "mid/6", + ); + }, +}); + +Deno.test({ + name: "Correctly resolves references.", + fn: () => { + const base = "http://a/b/c/d;p?q"; + assertStrictEquals(resolveReference("g:h", base), "g:h"); + assertStrictEquals(resolveReference("g", base), "http://a/b/c/g"); + assertStrictEquals( + resolveReference("./g", base), + "http://a/b/c/g", + ); + assertStrictEquals( + resolveReference("g/", base), + "http://a/b/c/g/", + ); + assertStrictEquals(resolveReference("/g", base), "http://a/g"); + assertStrictEquals(resolveReference("//g", base), "http://g"); + assertStrictEquals( + resolveReference("?y", base), + "http://a/b/c/d;p?y", + ); + assertStrictEquals( + resolveReference("g?y", base), + "http://a/b/c/g?y", + ); + assertStrictEquals( + resolveReference("#s", base), + "http://a/b/c/d;p?q#s", + ); + assertStrictEquals( + resolveReference("g#s", base), + "http://a/b/c/g#s", + ); + assertStrictEquals( + resolveReference("g?y#s", base), + "http://a/b/c/g?y#s", + ); + assertStrictEquals( + resolveReference(";x", base), + "http://a/b/c/;x", + ); + assertStrictEquals( + resolveReference("g;x", base), + "http://a/b/c/g;x", + ); + assertStrictEquals( + resolveReference("g;x?y#s", base), + "http://a/b/c/g;x?y#s", + ); + assertStrictEquals( + resolveReference("", base), + "http://a/b/c/d;p?q", + ); + assertStrictEquals(resolveReference(".", base), "http://a/b/c/"); + assertStrictEquals(resolveReference("./", base), "http://a/b/c/"); + assertStrictEquals(resolveReference("..", base), "http://a/b/"); + assertStrictEquals(resolveReference("../", base), "http://a/b/"); + assertStrictEquals(resolveReference("../g", base), "http://a/b/g"); + assertStrictEquals(resolveReference("../..", base), "http://a/"); + assertStrictEquals(resolveReference("../../", base), "http://a/"); + assertStrictEquals( + resolveReference("../../g", base), + "http://a/g", + ); + assertStrictEquals( + resolveReference("../../../g", base), + "http://a/g", + ); + assertStrictEquals( + resolveReference("../../../../g", base), + "http://a/g", + ); + assertStrictEquals(resolveReference("/./g", base), "http://a/g"); + assertStrictEquals(resolveReference("/../g", base), "http://a/g"); + assertStrictEquals( + resolveReference("g.", base), + "http://a/b/c/g.", + ); + assertStrictEquals( + resolveReference(".g", base), + "http://a/b/c/.g", + ); + assertStrictEquals( + resolveReference("g..", base), + "http://a/b/c/g..", + ); + assertStrictEquals( + resolveReference("..g", base), + "http://a/b/c/..g", + ); + assertStrictEquals( + resolveReference("./../g", base), + "http://a/b/g", + ); + assertStrictEquals( + resolveReference("./g/.", base), + "http://a/b/c/g/", + ); + assertStrictEquals( + resolveReference("g/./h", base), + "http://a/b/c/g/h", + ); + assertStrictEquals( + resolveReference("g/../h", base), + "http://a/b/c/h", + ); + assertStrictEquals( + resolveReference("g;x=1/./y", base), + "http://a/b/c/g;x=1/y", + ); + assertStrictEquals( + resolveReference("g;x=1/../y", base), + "http://a/b/c/y", + ); + assertStrictEquals( + resolveReference("g?y/./x", base), + "http://a/b/c/g?y/./x", + ); + assertStrictEquals( + resolveReference("g?y/../x", base), + "http://a/b/c/g?y/../x", + ); + assertStrictEquals( + resolveReference("g#s/./x", base), + "http://a/b/c/g#s/./x", + ); + assertStrictEquals( + resolveReference("g#s/../x", base), + "http://a/b/c/g#s/../x", + ); + assertStrictEquals(resolveReference("http:g", base), "http:g"); + }, +}); diff --git "a/i\302\267r\302\267i.js" "b/i\302\267r\302\267i.js" deleted file mode 100644 index 0892213..0000000 --- "a/i\302\267r\302\267i.js" +++ /dev/null @@ -1,78 +0,0 @@ -// ♓🌟 Piscēs ∷ i·r·i.js -// ==================================================================== -// -// Copyright © 2020, 2022 Lady [@ Lady’s Computer]. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at . - -//deno-lint-ignore-file no-unused-vars - -const sub·delims = String.raw`[!\$&'()*+,;=]`; -const gen·delims = String.raw`[:/?#\[\]@]`; -const reserved = String.raw`${gen·delims}|${sub·delims}`; -const unreserved = String.raw`[A-Za-z0-9\-\._~]`; -const pct·encoded = String.raw`%[0-9A-Fa-f][0-9A-Fa-f]`; -const dec·octet = String.raw - `[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]`; -const IPv4address = String.raw - `(?:${dec·octet})\.(?:${dec·octet})\.(?:${dec·octet})\.(?:${dec·octet})`; -const h16 = String.raw`[0-9A-Fa-f]{1,4}`; -const ls32 = String.raw`(?:${h16}):(?:${h16})|${IPv4address}`; -const IPv6address = String.raw - `(?:(?:${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}))?::`; -const IPvFuture = String.raw - `v[0-9A-Fa-f]{1,}\.(?:${unreserved}|${sub·delims}|:)`; -const IP·literal = String.raw`\[(?:${IPv6address}|${IPvFuture})\]`; -const port = String.raw`[0-9]*`; -const scheme = String.raw`[A-Za-z][A-Za-z0-9+\-\.]*`; -const iprivate = String.raw - `[\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]`; -const ucschar = String.raw - `[\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}]`; -const iunreserved = String.raw`[A-Za-z0-9\-\._~]|${ucschar}`; -const ipchar = String.raw - `${iunreserved}|${pct·encoded}|${sub·delims}|[:@]`; -const ifragment = String.raw`(?:${ipchar}|[/?])*`; -const iquery = String.raw`(?:${ipchar}|${iprivate}|[/?])*`; -const isegment·nz·nc = String.raw - `(?:${iunreserved}|${pct·encoded}|${sub·delims}|@)+`; -const isegment·nz = String.raw`(?:${ipchar})+`; -const isegment = String.raw`(?:${ipchar})*`; -const ipath·empty = String.raw``; -const ipath·rootless = String.raw - `(?:${isegment·nz})(?:/(?:${isegment}))*`; -const ipath·noscheme = String.raw - `(?:${isegment·nz·nc})(?:/(?:${isegment}))*`; -const ipath·absolute = String.raw - `/(?:(?:${isegment·nz})(?:/(?:${isegment}))*)?`; -const ipath·abempty = String.raw`(?:/(?:${isegment}))*`; -const ipath = String.raw - `${ipath·abempty}|${ipath·absolute}|${ipath·noscheme}|${ipath·rootless}|${ipath·empty}`; -const ireg·name = String.raw - `(?:${iunreserved}|${pct·encoded}|${sub·delims})*`; -const ihost = String.raw`${IP·literal}|${IPv4address}|${ireg·name}`; -const iuserinfo = String.raw - `(?:${iunreserved}|${pct·encoded}|${sub·delims}|:)*`; -const iauthority = String.raw - `(?:(${iuserinfo})@)?(${ihost})(?::(${port}))?`; -const irelative·part = String.raw - `//(${iauthority})(${ipath·abempty})|(${ipath·absolute})|(${ipath·noscheme})|(${ipath·empty})`; -const irelative·ref = String.raw - `(${irelative·part})(?:\?(${iquery}))?(?:#(${ifragment}))?`; -const ihier·part = String.raw - `//(${iauthority})(${ipath·abempty})|(${ipath·absolute})|(${ipath·rootless})|(${ipath·empty})`; -const absolute·IRI = String.raw - `(${scheme}):(${ihier·part})(?:\?(${iquery}))?`; -const IRI = String.raw - `(${scheme}):(${ihier·part})(?:\?(${iquery}))?(?:#(${ifragment}))?`; -const IRI·reference = String.raw`(${IRI})|(${irelative·ref})`; - -export const isI·R·I = ($) => - typeof $ == "string" && isI·R·I.regExp.test($); -Object.defineProperty(isI·R·I, "regExp", { - configurable: true, - enumerable: false, - get: () => new RegExp(`^(?:${IRI})$`, "u"), -}); diff --git a/mod.js b/mod.js index a25bc1e..dc8d6fc 100644 --- a/mod.js +++ b/mod.js @@ -9,6 +9,6 @@ export * from "./base64.js"; export * from "./collection.js"; -export * from "./i·r·i.js"; +export * from "./iri.js"; export * from "./numeric.js"; export * from "./object.js";