Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 1 addition & 39 deletions src/jsc/bindings/URLDecomposition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@

#include "URLDecomposition.h"

#include <wtf/text/StringToIntegerConversion.h>

namespace WebCore {

String URLDecomposition::origin() const
Expand Down Expand Up @@ -94,48 +92,14 @@ String URLDecomposition::host() const
return fullURL().hostAndPort();
}

static unsigned countASCIIDigits(StringView string)
{
unsigned length = string.length();
for (unsigned count = 0; count < length; ++count) {
if (!isASCIIDigit(string[count]))
return count;
}
return length;
}

void URLDecomposition::setHost(StringView value)
{
auto fullURL = this->fullURL();
if (value.isEmpty() && !fullURL.protocolIsFile() && fullURL.hasSpecialScheme())
return;

size_t separator = value.reverseFind(':');
if (!separator)
return;
fullURL.setHostAndPort(value);

if (fullURL.hasOpaquePath())
return;

// No port if no colon or rightmost colon is within the IPv6 section.
size_t ipv6Separator = value.reverseFind(']');
if (separator == notFound || (ipv6Separator != notFound && ipv6Separator > separator))
fullURL.setHost(value);
else {
// Multiple colons are acceptable only in case of IPv6.
if (value.find(':') != separator && ipv6Separator == notFound)
return;
unsigned portLength = countASCIIDigits(value.substring(separator + 1));
if (!portLength) {
fullURL.setHost(value.left(separator));
} else {
auto portNumber = parseInteger<uint16_t>(value.substring(separator + 1, portLength));
if (portNumber && WTF::isDefaultPortForProtocol(*portNumber, fullURL.protocol()))
fullURL.setHostAndPort(value.left(separator));
else
fullURL.setHostAndPort(value.left(separator + 1 + portLength));
}
}
if (fullURL.isValid())
setFullURL(fullURL);
}
Expand All @@ -150,8 +114,6 @@ void URLDecomposition::setHostname(StringView host)
auto fullURL = this->fullURL();
if (host.isEmpty() && !fullURL.protocolIsFile() && fullURL.hasSpecialScheme())
return;
if (fullURL.hasOpaquePath())
return;
fullURL.setHost(host);
if (fullURL.isValid())
setFullURL(fullURL);
Expand Down
46 changes: 46 additions & 0 deletions test/regression/issue/28661.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// https://github.com/oven-sh/bun/issues/28661
import { expect, test } from "bun:test";

test("URL.host setter replaces port when set to default port", () => {
// HTTP with default port 80 — port should be stripped
const u1 = new URL("http://localhost:3000/foo");
u1.host = "some-domain:80";
expect(u1.href).toBe("http://some-domain/foo");
expect(u1.host).toBe("some-domain");
expect(u1.port).toBe("");

// HTTPS with default port 443 — port should be stripped
const u2 = new URL("https://localhost:3000/foo");
u2.host = "some-domain:443";
expect(u2.href).toBe("https://some-domain/foo");
expect(u2.host).toBe("some-domain");
expect(u2.port).toBe("");

// Non-default port should be kept
const u3 = new URL("http://localhost:3000/foo");
u3.host = "some-domain:8080";
expect(u3.href).toBe("http://some-domain:8080/foo");
expect(u3.host).toBe("some-domain:8080");
expect(u3.port).toBe("8080");

// Host without port only changes hostname, preserves existing port
const u4 = new URL("http://localhost:3000/foo");
u4.host = "some-domain";
expect(u4.href).toBe("http://some-domain:3000/foo");
expect(u4.host).toBe("some-domain:3000");
expect(u4.port).toBe("3000");

// FTP with default port 21
const u5 = new URL("ftp://localhost:3000/foo");
u5.host = "some-domain:21";
expect(u5.href).toBe("ftp://some-domain/foo");
expect(u5.host).toBe("some-domain");
expect(u5.port).toBe("");

// IPv6 with default port 80
const u6 = new URL("http://[::1]:3000/foo");
u6.host = "[::1]:80";
expect(u6.href).toBe("http://[::1]/foo");
expect(u6.host).toBe("[::1]");
expect(u6.port).toBe("");
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Comment thread
robobun marked this conversation as resolved.
Resolved
Loading