diff --git a/docs/runtime/bun-apis.md b/docs/runtime/bun-apis.md index 3303907823..6f60236737 100644 --- a/docs/runtime/bun-apis.md +++ b/docs/runtime/bun-apis.md @@ -30,17 +30,20 @@ Click the link in the right column to jump to the associated documentation. --- - File I/O -- [`Bun.file`](/docs/api/file-io#reading-files-bun-file) [`Bun.write`](/docs/api/file-io#writing-files-bun-write) +- [`Bun.file`](/docs/api/file-io#reading-files-bun-file) + [`Bun.write`](/docs/api/file-io#writing-files-bun-write) --- - Child processes -- [`Bun.spawn`](/docs/api/spawn#spawn-a-process-bun-spawn) [`Bun.spawnSync`](/docs/api/spawn#blocking-api-bun-spawnsync) +- [`Bun.spawn`](/docs/api/spawn#spawn-a-process-bun-spawn) + [`Bun.spawnSync`](/docs/api/spawn#blocking-api-bun-spawnsync) --- - TCP -- [`Bun.listen`](/docs/api/tcp#start-a-server-bun-listen) [`Bun.connect`](/docs/api/tcp#start-a-server-bun-listen) +- [`Bun.listen`](/docs/api/tcp#start-a-server-bun-listen) + [`Bun.connect`](/docs/api/tcp#start-a-server-bun-listen) --- @@ -60,7 +63,8 @@ Click the link in the right column to jump to the associated documentation. --- - Hashing -- [`Bun.hash`](/docs/api/hashing#bun-hash) [`Bun.CryptoHasher`](/docs/api/hashing#bun-cryptohasher) +- [`Bun.hash`](/docs/api/hashing#bun-hash) + [`Bun.CryptoHasher`](/docs/api/hashing#bun-cryptohasher) --- @@ -100,6 +104,26 @@ Click the link in the right column to jump to the associated documentation. --- - Utilities -- [`Bun.version`](/docs/api/utils#bun-version) [`Bun.revision`](/docs/api/utils#bun-revision) [`Bun.env`](/docs/api/utils#bun-env) [`Bun.main`](/docs/api/utils#bun-main) [`Bun.sleep()`](/docs/api/utils#bun-sleep) [`Bun.sleepSync()`](/docs/api/utils#bun-sleepsync) [`Bun.which()`](/docs/api/utils#bun-which) [`Bun.peek()`](/docs/api/utils#bun-peek) [`Bun.openInEditor()`](/docs/api/utils#bun-openineditor) [`Bun.deepEquals()`](/docs/api/utils#bun-deepequals) [`Bun.escapeHTML()`](/docs/api/utils#bun-escapehtml) [`Bun.fileURLToPath()`](/docs/api/utils#bun-fileurltopath) [`Bun.pathToFileURL()`](/docs/api/utils#bun-pathtofileurl) [`Bun.gzipSync()`](/docs/api/utils#bun-gzipsync) [`Bun.gunzipSync()`](/docs/api/utils#bun-gunzipsync) [`Bun.deflateSync()`](/docs/api/utils#bun-deflatesync) [`Bun.inflateSync()`](/docs/api/utils#bun-inflatesync) [`Bun.inspect()`](/docs/api/utils#bun-inspect) [`Bun.nanoseconds()`](/docs/api/utils#bun-nanoseconds) [`Bun.readableStreamTo*()`](/docs/api/utils#bun-readablestreamto) [`Bun.resolveSync()`](/docs/api/utils#bun-resolvesync) +- [`Bun.version`](/docs/api/utils#bun-version) + [`Bun.revision`](/docs/api/utils#bun-revision) + [`Bun.env`](/docs/api/utils#bun-env) + [`Bun.main`](/docs/api/utils#bun-main) + [`Bun.sleep()`](/docs/api/utils#bun-sleep) + [`Bun.sleepSync()`](/docs/api/utils#bun-sleepsync) + [`Bun.which()`](/docs/api/utils#bun-which) + [`Bun.peek()`](/docs/api/utils#bun-peek) + [`Bun.openInEditor()`](/docs/api/utils#bun-openineditor) + [`Bun.deepEquals()`](/docs/api/utils#bun-deepequals) + [`Bun.escapeHTML()`](/docs/api/utils#bun-escapehtml) + [`Bun.fileURLToPath()`](/docs/api/utils#bun-fileurltopath) + [`Bun.pathToFileURL()`](/docs/api/utils#bun-pathtofileurl) + [`Bun.gzipSync()`](/docs/api/utils#bun-gzipsync) + [`Bun.gunzipSync()`](/docs/api/utils#bun-gunzipsync) + [`Bun.deflateSync()`](/docs/api/utils#bun-deflatesync) + [`Bun.inflateSync()`](/docs/api/utils#bun-inflatesync) + [`Bun.inspect()`](/docs/api/utils#bun-inspect) + [`Bun.nanoseconds()`](/docs/api/utils#bun-nanoseconds) + [`Bun.readableStreamTo*()`](/docs/api/utils#bun-readablestreamto) + [`Bun.resolveSync()`](/docs/api/utils#bun-resolvesync) {% /table %} diff --git a/docs/runtime/nodejs-apis.md b/docs/runtime/nodejs-apis.md index 439d309423..63075409b3 100644 --- a/docs/runtime/nodejs-apis.md +++ b/docs/runtime/nodejs-apis.md @@ -148,7 +148,7 @@ Some methods are not optimized yet. ### [`node:url`](https://nodejs.org/api/url.html) -🟡 Missing `domainToASCII` `domainToUnicode`. It's recommended to use `URL` and `URLSearchParams` globals instead. +🟢 Fully implemented. ### [`node:util`](https://nodejs.org/api/util.html) diff --git a/scripts/all-dependencies.sh b/scripts/all-dependencies.sh index 6815588518..06bf35fc6c 100755 --- a/scripts/all-dependencies.sh +++ b/scripts/all-dependencies.sh @@ -66,4 +66,4 @@ dep lshpack liblshpack.a if [ "$BUILT_ANY" -eq 0 ]; then printf "(run with -f to rebuild)\n" -fi \ No newline at end of file +fi diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 5db62e3562..4ea39198ab 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -60,6 +60,7 @@ #include "JavaScriptCore/WasmFaultSignalHandler.h" #include "wtf/Gigacage.h" #include "wtf/URL.h" +#include "wtf/URLParser.h" #include "wtf/text/ExternalStringImpl.h" #include "wtf/text/StringCommon.h" #include "wtf/text/StringImpl.h" @@ -138,6 +139,8 @@ #include "ProcessBindingConstants.h" +#include + #if ENABLE(REMOTE_INSPECTOR) #include "JavaScriptCore/RemoteInspectorServer.h" #endif @@ -1724,6 +1727,8 @@ JSC_DEFINE_CUSTOM_SETTER(noop_setter, static NeverDestroyed pathToFileURLString(MAKE_STATIC_STRING_IMPL("pathToFileURL")); static NeverDestroyed fileURLToPathString(MAKE_STATIC_STRING_IMPL("fileURLToPath")); +static NeverDestroyed domainToASCIIString(MAKE_STATIC_STRING_IMPL("domainToASCII")); +static NeverDestroyed domainToUnicodeString(MAKE_STATIC_STRING_IMPL("domainToUnicode")); enum ReadableStreamTag : int32_t { Invalid = -1, @@ -1779,6 +1784,142 @@ JSC_DEFINE_HOST_FUNCTION(jsReceiveMessageOnPort, (JSGlobalObject * lexicalGlobal return JSC::JSValue::encode(jsUndefined()); } +JSC_DEFINE_HOST_FUNCTION(functionDomainToASCII, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (callFrame->argumentCount() < 1) { + throwTypeError(globalObject, scope, "domainToASCII needs 1 argument"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + auto arg0 = callFrame->argument(0); + if (arg0.isUndefined()) + return JSC::JSValue::encode(jsUndefined()); + if (arg0.isNull()) + return JSC::JSValue::encode(jsNull()); + if (!arg0.isString()) { + throwTypeError(globalObject, scope, "the \"domain\" argument must be a string"_s); + return JSC::JSValue::encode(jsUndefined()); + } + + auto domain = arg0.toWTFString(globalObject); + if (domain.isNull()) + return JSC::JSValue::encode(jsUndefined()); + + // https://url.spec.whatwg.org/#forbidden-host-code-point + if ( + domain.contains(0x0000) || // U+0000 NULL + domain.contains(0x0009) || // U+0009 TAB + domain.contains(0x000A) || // U+000A LF + domain.contains(0x000D) || // U+000D CR + domain.contains(0x0020) || // U+0020 SPACE + domain.contains(0x0023) || // U+0023 (#) + domain.contains(0x002F) || // U+002F (/) + domain.contains(0x003A) || // U+003A (:) + domain.contains(0x003C) || // U+003C (<) + domain.contains(0x003E) || // U+003E (>) + domain.contains(0x003F) || // U+003F (?) + domain.contains(0x0040) || // U+0040 (@) + domain.contains(0x005B) || // U+005B ([) + domain.contains(0x005C) || // U+005C (\) + domain.contains(0x005D) || // U+005D (]) + domain.contains(0x005E) || // U+005E (^) + domain.contains(0x007C) // // U+007C (|). + ) + return JSC::JSValue::encode(jsEmptyString(vm)); + + if (domain.containsOnlyASCII()) + return JSC::JSValue::encode(arg0); + if (domain.is8Bit()) + domain.convertTo16Bit(); + + constexpr static int allowedNameToASCIIErrors = UIDNA_ERROR_EMPTY_LABEL | UIDNA_ERROR_LABEL_TOO_LONG | UIDNA_ERROR_DOMAIN_NAME_TOO_LONG | UIDNA_ERROR_LEADING_HYPHEN | UIDNA_ERROR_TRAILING_HYPHEN | UIDNA_ERROR_HYPHEN_3_4; + constexpr static size_t hostnameBufferLength = 2048; + + auto encoder = &WTF::URLParser::internationalDomainNameTranscoder(); + UChar hostnameBuffer[hostnameBufferLength]; + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo processingDetails = UIDNA_INFO_INITIALIZER; + int32_t numCharactersConverted = uidna_nameToASCII(encoder, StringView(domain).characters16(), domain.length(), hostnameBuffer, hostnameBufferLength, &processingDetails, &error); + + if (U_SUCCESS(error) && !(processingDetails.errors & ~allowedNameToASCIIErrors) && numCharactersConverted) { + return JSC::JSValue::encode(JSC::jsString(vm, WTF::String(hostnameBuffer, numCharactersConverted))); + } + throwTypeError(globalObject, scope, "domainToASCII failed"_s); + return JSC::JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(functionDomainToUnicode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (callFrame->argumentCount() < 1) { + throwTypeError(globalObject, scope, "domainToUnicode needs 1 argument"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + auto arg0 = callFrame->argument(0); + if (arg0.isUndefined()) + return JSC::JSValue::encode(jsUndefined()); + if (arg0.isNull()) + return JSC::JSValue::encode(jsNull()); + if (!arg0.isString()) { + throwTypeError(globalObject, scope, "the \"domain\" argument must be a string"_s); + return JSC::JSValue::encode(jsUndefined()); + } + + auto domain = arg0.toWTFString(globalObject); + if (domain.isNull()) + return JSC::JSValue::encode(jsUndefined()); + + // https://url.spec.whatwg.org/#forbidden-host-code-point + if ( + domain.contains(0x0000) || // U+0000 NULL + domain.contains(0x0009) || // U+0009 TAB + domain.contains(0x000A) || // U+000A LF + domain.contains(0x000D) || // U+000D CR + domain.contains(0x0020) || // U+0020 SPACE + domain.contains(0x0023) || // U+0023 (#) + domain.contains(0x002F) || // U+002F (/) + domain.contains(0x003A) || // U+003A (:) + domain.contains(0x003C) || // U+003C (<) + domain.contains(0x003E) || // U+003E (>) + domain.contains(0x003F) || // U+003F (?) + domain.contains(0x0040) || // U+0040 (@) + domain.contains(0x005B) || // U+005B ([) + domain.contains(0x005C) || // U+005C (\) + domain.contains(0x005D) || // U+005D (]) + domain.contains(0x005E) || // U+005E (^) + domain.contains(0x007C) // // U+007C (|). + ) + return JSC::JSValue::encode(jsEmptyString(vm)); + + if (!domain.is8Bit()) + // this function is only for undoing punycode so its okay if utf-16 text makes it out unchanged. + return JSC::JSValue::encode(arg0); + + domain.convertTo16Bit(); + + constexpr static int allowedNameToUnicodeErrors = UIDNA_ERROR_EMPTY_LABEL | UIDNA_ERROR_LABEL_TOO_LONG | UIDNA_ERROR_DOMAIN_NAME_TOO_LONG | UIDNA_ERROR_LEADING_HYPHEN | UIDNA_ERROR_TRAILING_HYPHEN | UIDNA_ERROR_HYPHEN_3_4; + constexpr static int hostnameBufferLength = 2048; + + auto encoder = &WTF::URLParser::internationalDomainNameTranscoder(); + UChar hostnameBuffer[hostnameBufferLength]; + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo processingDetails = UIDNA_INFO_INITIALIZER; + + int32_t numCharactersConverted = uidna_nameToUnicode(encoder, StringView(domain).characters16(), domain.length(), hostnameBuffer, hostnameBufferLength, &processingDetails, &error); + + if (U_SUCCESS(error) && !(processingDetails.errors & ~allowedNameToUnicodeErrors) && numCharactersConverted) { + return JSC::JSValue::encode(JSC::jsString(vm, WTF::String(hostnameBuffer, numCharactersConverted))); + } + throwTypeError(globalObject, scope, "domainToUnicode failed"_s); + return JSC::JSValue::encode(jsUndefined()); +} + extern "C" EncodedJSValue BunInternalFunction__syntaxHighlighter(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame); // we're trying out a new way to do this lazy loading @@ -1891,6 +2032,16 @@ JSC_DEFINE_HOST_FUNCTION(functionLazyLoad, return JSValue::encode(JSC::JSFunction::create(vm, globalObject, 1, "getStringWidth"_s, BunString__getStringWidth, ImplementationVisibility::Public)); } + if (string == "domainToASCII"_s) { + return JSValue::encode( + JSFunction::create(vm, globalObject, 1, domainToASCIIString, functionDomainToASCII, ImplementationVisibility::Public, NoIntrinsic)); + } + + if (string == "domainToUnicode"_s) { + return JSValue::encode( + JSFunction::create(vm, globalObject, 1, domainToUnicodeString, functionDomainToUnicode, ImplementationVisibility::Public, NoIntrinsic)); + } + if (string == "pathToFileURL"_s) { return JSValue::encode( JSFunction::create(vm, globalObject, 1, pathToFileURLString, functionPathToFileURL, ImplementationVisibility::Public, NoIntrinsic)); @@ -1916,6 +2067,7 @@ JSC_DEFINE_HOST_FUNCTION(functionLazyLoad, JSC::JSFunction::create(vm, globalObject, 0, "emitReadable"_s, jsReadable_emitReadable_, ImplementationVisibility::Public), 0); return JSValue::encode(obj); } + if (string == "events"_s) { return JSValue::encode(WebCore::JSEventEmitter::getConstructor(vm, globalObject)); } @@ -1970,6 +2122,7 @@ JSC_DEFINE_HOST_FUNCTION(functionLazyLoad, vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "getUnpackedSettings"_s)), JSC::JSFunction::create(vm, globalObject, 1, "getUnpackedSettings"_s, BUN__HTTP2__getUnpackedSettings, ImplementationVisibility::Public, NoIntrinsic), 0); return JSValue::encode(obj); } + if (string == "internal/tls"_s) { auto* obj = constructEmptyObject(globalObject); @@ -2053,9 +2206,13 @@ JSC_DEFINE_HOST_FUNCTION(functionLazyLoad, return JSC::JSValue::encode(obj); } +#if BUN_DEBUG + // this should always be unreachable unless in development and adding a new lazy value + // and it has not been added above yet. + ASSERT_NOT_REACHED(); +#else return JSC::JSValue::encode(JSC::jsUndefined()); - - break; +#endif } } } @@ -3713,6 +3870,7 @@ JSC_DEFINE_HOST_FUNCTION(functionGetDirectStreamDetails, (JSC::JSGlobalObject * return JSC::JSValue::encode(resultObject); } + JSC::GCClient::IsoSubspace* GlobalObject::subspaceForImpl(JSC::VM& vm) { return WebCore::subspaceForImpl( diff --git a/src/js/node/url.js b/src/js/node/url.js index 0a0fe6ce7d..554179c23c 100644 --- a/src/js/node/url.js +++ b/src/js/node/url.js @@ -824,6 +824,8 @@ function urlToHttpOptions(url) { const pathToFileURL = $lazy("pathToFileURL"); const fileURLToPath = $lazy("fileURLToPath"); +const domainToASCII = $lazy("domainToASCII"); +const domainToUnicode = $lazy("domainToUnicode"); export default { parse: urlParse, @@ -836,4 +838,6 @@ export default { pathToFileURL, fileURLToPath, urlToHttpOptions, + domainToASCII, + domainToUnicode, }; diff --git a/src/node-fallbacks/url.js b/src/node-fallbacks/url.js index 81c78001db..9311abad62 100644 --- a/src/node-fallbacks/url.js +++ b/src/node-fallbacks/url.js @@ -741,10 +741,14 @@ export { Url as Url }; export var pathToFileURL; export var fileURLToPath; +export var domainToASCII; +export var domainToUnicode; if (lazy) { pathToFileURL = lazy("pathToFileURL"); fileURLToPath = lazy("fileURLToPath"); + domainToASCII = lazy("domainToASCII"); + domainToUnicode = lazy("domainToUnicode"); } export default { @@ -755,6 +759,8 @@ export default { Url: Url, pathToFileURL: pathToFileURL, fileURLToPath: fileURLToPath, + domainToASCII, + domainToUnicode, URL, URLSearchParams, }; diff --git a/test/exports/bun-exports.bun-v0.6.11.json b/test/exports/bun-exports.bun-v0.6.11.json index 7b95eacae0..37062461b7 100644 --- a/test/exports/bun-exports.bun-v0.6.11.json +++ b/test/exports/bun-exports.bun-v0.6.11.json @@ -4452,6 +4452,8 @@ }, "URLSearchParams": "function", "Url": "function", + "domainToASCII": "function", + "domainToUnicode": "function", "fileURLToPath": "function", "format": "function", "parse": "function", @@ -4460,6 +4462,8 @@ "resolveObject": "function", "urlToHttpOptions": "function" }, + "domainToASCII": "function", + "domainToUnicode": "function", "fileURLToPath": "function", "format": "function", "parse": "function", diff --git a/test/exports/node-exports.bun-v0.6.11.json b/test/exports/node-exports.bun-v0.6.11.json index 0060b4c6a2..d324c88245 100644 --- a/test/exports/node-exports.bun-v0.6.11.json +++ b/test/exports/node-exports.bun-v0.6.11.json @@ -4396,6 +4396,8 @@ }, "URLSearchParams": "function", "Url": "function", + "domainToASCII": "function", + "domainToUnicode": "function", "fileURLToPath": "function", "format": "function", "parse": "function", @@ -4404,6 +4406,8 @@ "resolveObject": "function", "urlToHttpOptions": "function" }, + "domainToASCII": "function", + "domainToUnicode": "function", "fileURLToPath": "function", "format": "function", "parse": "function", @@ -7434,6 +7438,8 @@ "fileURLToPath": "function", "nanoseconds": "function", "password": "object", + "domainToASCII": "function", + "domainToUnicode": "function", "pathToFileURL": "function", "peek": "function", "plugin": "function", @@ -8016,6 +8022,8 @@ }, "URLSearchParams": "function", "Url": "function", + "domainToASCII": "function", + "domainToUnicode": "function", "fileURLToPath": "function", "format": "function", "parse": "function", @@ -8027,6 +8035,8 @@ "fileURLToPath": "function", "format": "function", "parse": "function", + "domainToASCII": "function", + "domainToUnicode": "function", "pathToFileURL": "function", "resolve": "function", "resolveObject": "function", diff --git a/test/js/node/url/url-domain-ascii-unicode.test.js b/test/js/node/url/url-domain-ascii-unicode.test.js index 8c3964d018..1f6c94fc3d 100644 --- a/test/js/node/url/url-domain-ascii-unicode.test.js +++ b/test/js/node/url/url-domain-ascii-unicode.test.js @@ -1,31 +1,101 @@ -import { describe, test } from "bun:test"; -import assert from "node:assert"; +import { describe, test, expect } from "bun:test"; import url from "node:url"; -const domainToASCII = url.domainToASCII; -const domainToUnicode = url.domainToUnicode; +const pairs = [ + ["ıíd", "xn--d-iga7r"], + ["يٴ", "xn--mhb8f"], + ["www.ϧƽəʐ.com", "www.xn--cja62apfr6c.com"], + ["новини.com", "xn--b1amarcd.com"], + ["名がドメイン.com", "xn--v8jxj3d1dzdz08w.com"], + ["افغانستا.icom.museum", "xn--mgbaal8b0b9b2b.icom.museum"], + ["الجزائر.icom.fake", "xn--lgbbat1ad8j.icom.fake"], + ["भारत.org", "xn--h2brj9c.org"], + ["افغانستا.icom.museum", "xn--mgbaal8b0b9b2b.icom.museum"], + ["الجزائر.icom.museum", "xn--lgbbat1ad8j.icom.museum"], + ["österreich.icom.museum", "xn--sterreich-z7a.icom.museum"], + ["বাংলাদেশ.icom.museum", "xn--54b6eqazv8bc7e.icom.museum"], + ["беларусь.icom.museum", "xn--80abmy0agn7e.icom.museum"], + ["belgië.icom.museum", "xn--belgi-rsa.icom.museum"], + ["българия.icom.museum", "xn--80abgvm6a7d2b.icom.museum"], + ["تشادر.icom.museum", "xn--mgbfqim.icom.museum"], + ["中国.icom.museum", "xn--fiqs8s.icom.museum"], + ["القمر.icom.museum", "xn--mgbu4chg.icom.museum"], + ["κυπρος.icom.museum", "xn--vxakcego.icom.museum"], + ["českárepublika.icom.museum", "xn--eskrepublika-ebb62d.icom.museum"], + ["مصر.icom.museum", "xn--wgbh1c.icom.museum"], + ["ελλάδα.icom.museum", "xn--hxakic4aa.icom.museum"], + ["magyarország.icom.museum", "xn--magyarorszg-t7a.icom.museum"], + ["ísland.icom.museum", "xn--sland-ysa.icom.museum"], + ["भारत.icom.museum", "xn--h2brj9c.icom.museum"], + ["ايران.icom.museum", "xn--mgba3a4fra.icom.museum"], + ["éire.icom.museum", "xn--ire-9la.icom.museum"], + ["איקו״ם.ישראל.museum", "xn--4dbklr2c8d.xn--4dbrk0ce.museum"], + ["日本.icom.museum", "xn--wgv71a.icom.museum"], + ["الأردن.icom.museum", "xn--igbhzh7gpa.icom.museum"], + ["қазақстан.icom.museum", "xn--80aaa0a6awh12ed.icom.museum"], + ["한국.icom.museum", "xn--3e0b707e.icom.museum"], + ["кыргызстан.icom.museum", "xn--80afmksoji0fc.icom.museum"], + ["ລາວ.icom.museum", "xn--q7ce6a.icom.museum"], + ["لبنان.icom.museum", "xn--mgbb7fjb.icom.museum"], + ["македонија.icom.museum", "xn--80aaldqjmmi6x.icom.museum"], + ["موريتانيا.icom.museum", "xn--mgbah1a3hjkrd.icom.museum"], + ["méxico.icom.museum", "xn--mxico-bsa.icom.museum"], + ["монголулс.icom.museum", "xn--c1aqabffc0aq.icom.museum"], + ["المغرب.icom.museum", "xn--mgbc0a9azcg.icom.museum"], + ["नेपाल.icom.museum", "xn--l2bey1c2b.icom.museum"], + ["عمان.icom.museum", "xn--mgb9awbf.icom.museum"], + ["قطر.icom.museum", "xn--wgbl6a.icom.museum"], + ["românia.icom.museum", "xn--romnia-yta.icom.museum"], + ["россия.иком.museum", "xn--h1alffa9f.xn--h1aegh.museum"], + ["србијаицрнагора.иком.museum", "xn--80aaabm1ab4blmeec9e7n.xn--h1aegh.museum"], + ["இலங்கை.icom.museum", "xn--xkc2al3hye2a.icom.museum"], + ["españa.icom.museum", "xn--espaa-rta.icom.museum"], + ["ไทย.icom.museum", "xn--o3cw4h.icom.museum"], + ["تونس.icom.museum", "xn--pgbs0dh.icom.museum"], + ["türkiye.icom.museum", "xn--trkiye-3ya.icom.museum"], + ["украина.icom.museum", "xn--80aaxgrpt.icom.museum"], + ["việtnam.icom.museum", "xn--vitnam-jk8b.icom.museum"], + [`${"a".repeat(64)}.com`, `${"a".repeat(64)}.com`], + [`${`${"a".repeat(64)}.`.repeat(4)}com`, `${`${"a".repeat(64)}.`.repeat(4)}com`], + ["r4---sn-a5mlrn7s.gevideo.com", "r4---sn-a5mlrn7s.gevideo.com"], + ["-sn-a5mlrn7s.gevideo.com", "-sn-a5mlrn7s.gevideo.com"], + ["sn-a5mlrn7s-.gevideo.com", "sn-a5mlrn7s-.gevideo.com"], + ["-sn-a5mlrn7s-.gevideo.com", "-sn-a5mlrn7s-.gevideo.com"], + ["-sn--a5mlrn7s-.gevideo.com", "-sn--a5mlrn7s-.gevideo.com"], +]; -// TODO: Support url.domainToASCII and url.domainToUnicode. -describe.todo("url.domainToASCII and url.domainToUnicode", () => { - test("convert from unicode to ascii and back", () => { - const domainWithASCII = [ - ["ıíd", "xn--d-iga7r"], - ["يٴ", "xn--mhb8f"], - ["www.ϧƽəʐ.com", "www.xn--cja62apfr6c.com"], - ["новини.com", "xn--b1amarcd.com"], - ["名がドメイン.com", "xn--v8jxj3d1dzdz08w.com"], - ["افغانستا.icom.museum", "xn--mgbaal8b0b9b2b.icom.museum"], - ["الجزائر.icom.fake", "xn--lgbbat1ad8j.icom.fake"], - ["भारत.org", "xn--h2brj9c.org"], - ]; +const invalids = [ + ["@", ""], + ["a@b", ""], + [null, null], + [undefined, undefined], + ["2001:0db8:85a3:0000:0000:8a2e:0370:7334", ""], +]; - domainWithASCII.forEach(pair => { - const domain = pair[0]; - const ascii = pair[1]; - const domainConvertedToASCII = domainToASCII(domain); - assert.strictEqual(domainConvertedToASCII, ascii); - const asciiConvertedToUnicode = domainToUnicode(ascii); - assert.strictEqual(asciiConvertedToUnicode, domain); +describe("url.domainToASCII", () => { + for (const [domain, ascii] of pairs) { + test(`convert from '${domain}' to '${ascii}'`, () => { + const domainConvertedToASCII = url.domainToASCII(domain); + expect(domainConvertedToASCII).toEqual(ascii); }); - }); + } + for (const [input, expected] of invalids) { + test(`-> '${input}' is '${expected}'`, () => { + expect(url.domainToASCII(input)).toEqual(expected); + }); + } +}); + +describe("url.domainToUnicode", () => { + for (const [domain, ascii] of pairs) { + test(`convert from '${ascii}' to '${domain}'`, () => { + const asciiConvertedToUnicode = url.domainToUnicode(ascii); + expect(asciiConvertedToUnicode).toEqual(domain); + }); + } + for (const [input, expected] of invalids) { + test(`-> '${input}' is '${expected}'`, () => { + expect(url.domainToASCII(input)).toEqual(expected); + }); + } });