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 2304cbb
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 2 deletions.
5 changes: 5 additions & 0 deletions src/node/internal/url.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// 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
export function domainToASCII(domain: string): string;
export function domainToUnicode(domain: string): string;
33 changes: 33 additions & 0 deletions src/node/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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
/* 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, URLSearchParams } = globalThis;

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

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

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

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

export { URL, URLSearchParams };

export default {
domainToASCII,
domainToUnicode,
URL,
URLSearchParams,
};
7 changes: 7 additions & 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 Expand Up @@ -138,6 +139,12 @@ wd_test(
data = ["tests/string-decoder-test.js"],
)

wd_test(
src = "tests/url-nodejs-test.wd-test",
args = ["--experimental"],
data = ["tests/url-nodejs-test.js"],
)

wd_test(
src = "tests/util-nodejs-test.wd-test",
args = ["--experimental"],
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
79 changes: 79 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,79 @@
// 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'],
['aaa', 'aaa'],
];

for (const [domain, ascii] of domainWithASCII) {
strictEqual(domainToASCII(domain), ascii);
strictEqual(domainToUnicode(ascii), domain);
}
},
};

// 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"],
)
),
],
);
47 changes: 47 additions & 0 deletions src/workerd/api/node/url.c++
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// 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 {

namespace {

// Implementation is used by `domainToASCII` and `domainToUnicode`
kj::Maybe<std::string> GetHostName(kj::StringPtr domain) {
if (domain.size() == 0) {
return kj::none;
}

// It is important to have an initial value that contains a special scheme.
// Since it will change the implementation of `set_hostname` according to URL
// spec.
auto out = ada::parse<ada::url>("ws://x");
JSG_REQUIRE(out.has_value(), Error, "URL parsing failed"_kj);
if (!out->set_hostname({domain.begin(), domain.size()})) {
return kj::none;
}
return out->get_hostname();
}

} // namespace

jsg::JsString UrlUtil::domainToASCII(jsg::Lock& js, kj::String domain) {
KJ_IF_SOME(hostname, GetHostName(domain)) {
return js.str(kj::StringPtr(hostname.data(), hostname.size()));
}
return js.str(""_kj);
}

jsg::JsString UrlUtil::domainToUnicode(jsg::Lock& js, kj::String domain) {
KJ_IF_SOME(hostname, GetHostName(domain)) {
auto result = ada::idna::to_unicode(hostname);
return js.str(kj::StringPtr(result.data(), result.size()));
}

return js.str(""_kj);
}

} // 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&) {}

jsg::JsString domainToUnicode(jsg::Lock& js, kj::String domain);
jsg::JsString domainToASCII(jsg::Lock& js, 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 2304cbb

Please sign in to comment.