Skip to content

Commit

Permalink
implement domainToASCII and domainToUnicode
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Aug 29, 2024
1 parent 1902a59 commit 7035c6c
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/node/internal/url.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export function domainToASCII(domain: string): string;
export function domainToUnicode(domain: string): string;
31 changes: 31 additions & 0 deletions src/node/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { default as urlUtil } from 'node-internal:url';
import { ERR_MISSING_ARGS } from 'node-internal:internal_errors';

const URL = globalThis.URL;
const URLSearchParams = globalThis.URLSearchParams;

function domainToASCII(domain: string): string {
if (arguments.length < 1) {
throw new ERR_MISSING_ARGS('domain');
}

return urlUtil.domainToASCII(`${domain}`);
}

function domainToUnicode(domain: string): string {
if (arguments.length < 1) {
throw new ERR_MISSING_ARGS('domain');
}

return urlUtil.domainToASCII(`${domain}`);
}

export { domainToASCII, domainToUnicode, URL, URLSearchParams };

export default {
domainToASCII,
domainToUnicode,
URL,
URLSearchParams,
};
1 change: 1 addition & 0 deletions src/workerd/api/node/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ wd_cc_library(
),
hdrs = glob(["**/*.h"]),
implementation_deps = [
"@ada-url",
"@capnp-cpp//src/kj/compat:kj-gzip",
"@nbytes",
"@simdutf",
Expand Down
6 changes: 4 additions & 2 deletions src/workerd/api/node/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "buffer.h"
#include "crypto.h"
#include "diagnostics-channel.h"
#include "url.h"
#include "util.h"
#include "zlib-util.h"
#include <workerd/jsg/jsg.h>
Expand Down Expand Up @@ -44,7 +45,8 @@ class CompatibilityFlags: public jsg::Object {
V(CryptoImpl, "node-internal:crypto") \
V(UtilModule, "node-internal:util") \
V(DiagnosticsChannelModule, "node-internal:diagnostics_channel") \
V(ZlibUtil, "node-internal:zlib")
V(ZlibUtil, "node-internal:zlib") \
V(UrlUtil, "node-internal:url")

// Add to the NODEJS_MODULES_EXPERIMENTAL list any currently in-development
// node.js compat C++ modules that should be guarded by the experimental compat
Expand Down Expand Up @@ -135,4 +137,4 @@ kj::Own<jsg::modules::ModuleBundle> getExternalNodeJsCompatModuleBundle(auto fea
#define EW_NODE_ISOLATE_TYPES \
api::node::CompatibilityFlags, EW_NODE_BUFFER_ISOLATE_TYPES, EW_NODE_CRYPTO_ISOLATE_TYPES, \
EW_NODE_DIAGNOSTICCHANNEL_ISOLATE_TYPES, EW_NODE_ASYNCHOOKS_ISOLATE_TYPES, \
EW_NODE_UTIL_ISOLATE_TYPES, EW_NODE_ZLIB_ISOLATE_TYPES
EW_NODE_UTIL_ISOLATE_TYPES, EW_NODE_ZLIB_ISOLATE_TYPES, EW_NODE_URL_ISOLATE_TYPES
103 changes: 103 additions & 0 deletions src/workerd/api/node/tests/url-nodejs-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0
//
// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

import { strictEqual, throws, ok as assert } from 'node:assert';
import {
domainToASCII,
domainToUnicode,
URL as URLImpl,
URLSearchParams as URLSearchParamsImpl,
} from 'node:url';

// Tests are taken from:
// https://github.com/nodejs/node/blob/321a14b36d6b3304aedfd183e12ddba35dc704bd/test/parallel/test-url-domain-ascii-unicode.js
export const testDomainAsciiUnicode = {
test() {
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'],
];

for (const pair of domainWithASCII) {
const domain = pair[0];
const ascii = pair[1];
const domainConvertedToASCII = domainToASCII(domain);
strictEqual(domainConvertedToASCII, ascii);
const asciiConvertedToUnicode = domainToUnicode(ascii);
strictEqual(asciiConvertedToUnicode, domain);
}

{
for (const [i, { ascii, unicode }] of tests.entries()) {
strictEqual(ascii, domainToASCII(unicode), `domainToASCII(${i + 1})`);
strictEqual(
unicode,
domainToUnicode(ascii),
`domainToUnicode(${i + 1})`
);
strictEqual(
ascii,
domainToASCII(domainToUnicode(ascii)),
`domainToASCII(domainToUnicode(${i + 1}))`
);
strictEqual(
unicode,
domainToUnicode(domainToASCII(unicode)),
`domainToUnicode(domainToASCII(${i + 1}))`
);
}
}
},
};

// Tests are taken from:
// https://github.com/nodejs/node/blob/321a14b36d6b3304aedfd183e12ddba35dc704bd/test/parallel/test-whatwg-url-custom-domainto.js
export const whatwgURLCustomDomainTo = {
test() {
{
const expectedError = { code: 'ERR_MISSING_ARGS', name: 'TypeError' };
throws(() => domainToASCII(), expectedError);
throws(() => domainToUnicode(), expectedError);
strictEqual(domainToASCII(undefined), 'undefined');
strictEqual(domainToUnicode(undefined), 'undefined');
}
},
};

export const urlAndSearchParams = {
test() {
assert(URLImpl, 'URL should be exported from node:url');
assert(
URLSearchParamsImpl,
'URLSearchParams should be exported by node:url'
);
},
};
15 changes: 15 additions & 0 deletions src/workerd/api/node/tests/url-nodejs-test.wd-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Workerd = import "/workerd/workerd.capnp";

const unitTests :Workerd.Config = (
services = [
( name = "nodejs-url-test",
worker = (
modules = [
(name = "worker", esModule = embed "url-nodejs-test.js")
],
compatibilityDate = "2023-10-01",
compatibilityFlags = ["nodejs_compat"],
)
),
],
);
39 changes: 39 additions & 0 deletions src/workerd/api/node/url.c++
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0
#include "url.h"

#include <ada.h>

namespace workerd::api::node {

kj::ArrayPtr<const char> UrlUtil::domainToUnicode(kj::String domain) {
if (domain.size() == 0) {
return {};
}

auto out = ada::parse<ada::url_aggregator>("ws://x");
JSG_REQUIRE(out.has_value(), Error, "URL parsing failed"_kj);
if (!out->set_hostname({domain.begin(), domain.size()})) {
return {};
}
auto result = out->get_hostname();
return kj::ArrayPtr<const char>(result.data(), result.size());
}

kj::ArrayPtr<const char> UrlUtil::domainToASCII(kj::String domain) {
if (domain.size() == 0) {
return {};
}

auto out = ada::parse<ada::url_aggregator>("ws://x");
JSG_REQUIRE(out.has_value(), Error, "URL parsing failed"_kj);
if (!out->set_hostname({domain.begin(), domain.size()})) {
return {};
}

auto result = ada::idna::to_unicode(out->get_hostname());
return kj::ArrayPtr<const char>(result.data(), result.size());
}

} // namespace workerd::api::node
27 changes: 27 additions & 0 deletions src/workerd/api/node/url.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0
#pragma once

#include <kj/string.h>
#include <workerd/jsg/jsg.h>

namespace workerd::api::node {

class UrlUtil final: public jsg::Object {
public:
UrlUtil() = default;
UrlUtil(jsg::Lock&, const jsg::Url&) {}

kj::ArrayPtr<const char> domainToUnicode(kj::String domain);
kj::ArrayPtr<const char> domainToASCII(kj::String domain);

JSG_RESOURCE_TYPE(UrlUtil) {
JSG_METHOD(domainToUnicode);
JSG_METHOD(domainToASCII);
}
};

#define EW_NODE_URL_ISOLATE_TYPES api::node::UrlUtil

} // namespace workerd::api::node

0 comments on commit 7035c6c

Please sign in to comment.