Compare commits

...

4 Commits

Author SHA1 Message Date
Claude Bot
eb47be603e Fix url.format() issues revealed by enabling Intl
This commit fixes three bugs in node:url's format() function that were
hidden before enabling v8 i18n support:

1. IPv6 hostname bracket duplication: When formatting a URL with
   hostname: "[::]", the code would add extra brackets, producing
   "http://[[::]]//" instead of "http://[::]//". Fixed by checking if the
   hostname already has brackets before adding them.

2. Incomplete hash encoding in search: The code only replaced the first
   '#' in the search string, not all of them. Changed from replace("#")
   to replace(/#/g) to encode all hash symbols.

3. Missing slashes for file:// URLs: The logic for adding "//" prefix
   failed for file:// protocol URLs with no hostname. Fixed by matching
   Node.js's logic which explicitly checks for the "file" protocol and
   adds "//" even when there's no host.

All changes now match Node.js's implementation in vendor/node/lib/url.js.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 22:08:27 +00:00
Claude Bot
f8c9435f65 Fix url.format() to support WHATWG URL objects with options
The test `test-url-format-whatwg.js` was previously hidden due to missing
Intl support. With Intl enabled, it revealed that `url.format()` didn't
properly handle WHATWG URL objects with options like `auth`, `fragment`,
`search`, and `unicode`.

This change adds `formatWhatwgUrl()` function to handle WHATWG URLs with
options, supporting:
- `auth`: Control inclusion of username/password (default: true)
- `fragment`: Control inclusion of hash fragment (default: true)
- `search`: Control inclusion of query string (default: true)
- `unicode`: Convert punycode hostnames to unicode (default: false)

The implementation also handles edge cases like URLs without hosts (e.g.,
`tel:123`) and domainToUnicode failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 22:05:52 +00:00
Claude Bot
fb10af7ca4 Merge branch 'main' into claude/fix-v8-i18n-support-typo 2025-11-03 21:55:20 +00:00
Claude Bot
6f510841da Fix typo in process.config.variables.v8_enable_i18n_support
Changed `v8_enable_i8n_support` to `v8_enable_i18n_support` to match
Node.js behavior. Added test to verify the property is set correctly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 01:04:57 +00:00
3 changed files with 98 additions and 11 deletions

View File

@@ -2151,7 +2151,7 @@ static JSValue constructProcessConfigObject(VM& vm, JSObject* processObject)
// }
JSC::JSObject* config = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 2);
JSC::JSObject* variables = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 2);
variables->putDirect(vm, JSC::Identifier::fromString(vm, "v8_enable_i8n_support"_s), JSC::jsNumber(1), 0);
variables->putDirect(vm, JSC::Identifier::fromString(vm, "v8_enable_i18n_support"_s), JSC::jsNumber(1), 0);
variables->putDirect(vm, JSC::Identifier::fromString(vm, "enable_lto"_s), JSC::jsBoolean(false), 0);
variables->putDirect(vm, JSC::Identifier::fromString(vm, "node_module_version"_s), JSC::jsNumber(REPORTED_NODEJS_ABI_VERSION), 0);
variables->putDirect(vm, JSC::Identifier::fromString(vm, "napi_build_version"_s), JSC::jsNumber(Napi::DEFAULT_NAPI_VERSION), 0);

View File

@@ -461,8 +461,8 @@ function getHostname(self, rest, hostname: string, url) {
}
// format a parsed object into a url string
declare function urlFormat(urlObject: string | URL | Url): string;
function urlFormat(urlObject: unknown) {
declare function urlFormat(urlObject: string | URL | Url, options?: any): string;
function urlFormat(urlObject: unknown, options?: any) {
/*
* ensure it's an object, and not a string url.
* If it's an obj, this is a no-op.
@@ -476,12 +476,88 @@ function urlFormat(urlObject: unknown) {
throw $ERR_INVALID_ARG_TYPE("urlObject", ["Object", "string"], urlObject);
}
// Handle WHATWG URL objects with options
if (urlObject instanceof URL) {
return formatWhatwgUrl(urlObject, options);
}
if (!(urlObject instanceof Url)) {
return Url.prototype.format.$call(urlObject);
}
return urlObject.format();
}
function formatWhatwgUrl(url: URL, options?: any) {
if (options !== undefined && options !== null && typeof options !== "object") {
throw $ERR_INVALID_ARG_TYPE("options", "object", options);
}
let fragment = true;
let unicode = false;
let search = true;
let auth = true;
if (options) {
if (options.fragment != null) {
fragment = Boolean(options.fragment);
}
if (options.unicode != null) {
unicode = Boolean(options.unicode);
}
if (options.search != null) {
search = Boolean(options.search);
}
if (options.auth != null) {
auth = Boolean(options.auth);
}
}
let result = url.protocol;
// Only add // for URLs with host
const hasHost = url.host !== "";
if (hasHost) {
result += "//";
if (auth && (url.username || url.password)) {
result += url.username;
if (url.password) {
result += ":" + url.password;
}
result += "@";
}
let hostname = url.hostname;
if (unicode && hostname) {
try {
hostname = domainToUnicode(hostname);
} catch (e) {
// If domainToUnicode fails, use the original hostname
}
}
result += hostname;
if (url.port) {
result += ":" + url.port;
}
}
result += url.pathname;
if (search && url.search) {
result += url.search;
}
if (fragment && url.hash) {
result += url.hash;
}
return result;
}
Url.prototype.format = function format() {
var auth: string = this.auth || "";
if (auth) {
@@ -499,7 +575,9 @@ Url.prototype.format = function format() {
if (this.host) {
host = auth + this.host;
} else if (this.hostname) {
host = auth + (this.hostname.indexOf(":") === -1 ? this.hostname : "[" + this.hostname + "]");
// Check if hostname contains ':' (IPv6) and doesn't already have brackets
const needsBrackets = this.hostname.indexOf(":") !== -1 && !isIpv6Hostname(this.hostname);
host = auth + (needsBrackets ? "[" + this.hostname + "]" : this.hostname);
if (this.port) {
host += ":" + this.port;
}
@@ -519,13 +597,21 @@ Url.prototype.format = function format() {
* only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
* unless they had them to begin with.
*/
if (this.slashes || ((!protocol || slashedProtocol[protocol]) && host.length > 0)) {
host = "//" + (host || "");
if (pathname && pathname.charAt(0) !== "/") {
pathname = "/" + pathname;
if (this.slashes || slashedProtocol[protocol]) {
if (this.slashes || host) {
if (pathname && pathname.charAt(0) !== "/") {
pathname = "/" + pathname;
}
host = "//" + host;
} else if (
protocol.length >= 4 &&
protocol.$charCodeAt(0) === 102 /* f */ &&
protocol.$charCodeAt(1) === 105 /* i */ &&
protocol.$charCodeAt(2) === 108 /* l */ &&
protocol.$charCodeAt(3) === 101 /* e */
) {
host = "//";
}
} else if (!host) {
host = "";
}
if (hash && hash.charAt(0) !== "#") {
@@ -538,7 +624,7 @@ Url.prototype.format = function format() {
pathname = pathname.replace(/[?#]/g, function (match) {
return encodeURIComponent(match);
});
search = search.replace("#", "%23");
search = search.replace(/#/g, "%23");
return protocol + host + pathname + search + hash;
};

View File

@@ -298,6 +298,7 @@ it("process.config", () => {
expect(process.config.variables.clang).toBeNumber();
expect(process.config.variables.host_arch).toBeDefined();
expect(process.config.variables.target_arch).toBeDefined();
expect(process.config.variables.v8_enable_i18n_support).toBe(1);
});
it("process.execArgv", () => {