node:url implement domainToASCII and domainToUnicode (#9257)

* node:url implement domainToASCII and domainToUnicode

* fix arg checks

* remove unneeded use of WTF::Vector

* tidy

* throw a js error if icu fails

* add a ton more tests, fix ascii guard, upconvert latin1

* even more tests

* add a comment for this guard

* use ASSERT_NOT_REACHED() instead of raise(SIGABRT)
This commit is contained in:
Meghan Denny
2024-03-05 19:46:38 -08:00
committed by GitHub
parent c837903e4e
commit bb3295ba84
9 changed files with 310 additions and 34 deletions

View File

@@ -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 %}

View File

@@ -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)

View File

@@ -66,4 +66,4 @@ dep lshpack liblshpack.a
if [ "$BUILT_ANY" -eq 0 ]; then
printf "(run with -f to rebuild)\n"
fi
fi

View File

@@ -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 <unicode/uidna.h>
#if ENABLE(REMOTE_INSPECTOR)
#include "JavaScriptCore/RemoteInspectorServer.h"
#endif
@@ -1724,6 +1727,8 @@ JSC_DEFINE_CUSTOM_SETTER(noop_setter,
static NeverDestroyed<const String> pathToFileURLString(MAKE_STATIC_STRING_IMPL("pathToFileURL"));
static NeverDestroyed<const String> fileURLToPathString(MAKE_STATIC_STRING_IMPL("fileURLToPath"));
static NeverDestroyed<const String> domainToASCIIString(MAKE_STATIC_STRING_IMPL("domainToASCII"));
static NeverDestroyed<const String> 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<GlobalObject, WebCore::UseCustomHeapCellType::Yes>(

View File

@@ -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,
};

View File

@@ -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,
};

View File

@@ -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",

View File

@@ -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",

View File

@@ -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);
});
}
});