mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-21 20:24:42 +00:00
Merge pull request #1750 from lightpanda-io/url_set_username_password
Add setters to URL.username and URL.password
This commit is contained in:
@@ -167,17 +167,17 @@ pub fn ensureEncoded(allocator: Allocator, url: [:0]const u8) ![:0]const u8 {
|
||||
const query_end = if (query_start) |_| (fragment_start orelse url.len) else path_end;
|
||||
|
||||
const path_to_encode = url[path_start..path_end];
|
||||
const encoded_path = try percentEncodeSegment(allocator, path_to_encode, true);
|
||||
const encoded_path = try percentEncodeSegment(allocator, path_to_encode, .path);
|
||||
|
||||
const encoded_query = if (query_start) |qs| blk: {
|
||||
const query_to_encode = url[qs + 1 .. query_end];
|
||||
const encoded = try percentEncodeSegment(allocator, query_to_encode, false);
|
||||
const encoded = try percentEncodeSegment(allocator, query_to_encode, .query);
|
||||
break :blk encoded;
|
||||
} else null;
|
||||
|
||||
const encoded_fragment = if (fragment_start) |fs| blk: {
|
||||
const fragment_to_encode = url[fs + 1 ..];
|
||||
const encoded = try percentEncodeSegment(allocator, fragment_to_encode, false);
|
||||
const encoded = try percentEncodeSegment(allocator, fragment_to_encode, .query);
|
||||
break :blk encoded;
|
||||
} else null;
|
||||
|
||||
@@ -204,11 +204,13 @@ pub fn ensureEncoded(allocator: Allocator, url: [:0]const u8) ![:0]const u8 {
|
||||
return buf.items[0 .. buf.items.len - 1 :0];
|
||||
}
|
||||
|
||||
fn percentEncodeSegment(allocator: Allocator, segment: []const u8, comptime is_path: bool) ![]const u8 {
|
||||
const EncodeSet = enum { path, query, userinfo };
|
||||
|
||||
fn percentEncodeSegment(allocator: Allocator, segment: []const u8, comptime encode_set: EncodeSet) ![]const u8 {
|
||||
// Check if encoding is needed
|
||||
var needs_encoding = false;
|
||||
for (segment) |c| {
|
||||
if (shouldPercentEncode(c, is_path)) {
|
||||
if (shouldPercentEncode(c, encode_set)) {
|
||||
needs_encoding = true;
|
||||
break;
|
||||
}
|
||||
@@ -235,7 +237,7 @@ fn percentEncodeSegment(allocator: Allocator, segment: []const u8, comptime is_p
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldPercentEncode(c, is_path)) {
|
||||
if (shouldPercentEncode(c, encode_set)) {
|
||||
try buf.writer(allocator).print("%{X:0>2}", .{c});
|
||||
} else {
|
||||
try buf.append(allocator, c);
|
||||
@@ -245,16 +247,17 @@ fn percentEncodeSegment(allocator: Allocator, segment: []const u8, comptime is_p
|
||||
return buf.items;
|
||||
}
|
||||
|
||||
fn shouldPercentEncode(c: u8, comptime is_path: bool) bool {
|
||||
fn shouldPercentEncode(c: u8, comptime encode_set: EncodeSet) bool {
|
||||
return switch (c) {
|
||||
// Unreserved characters (RFC 3986)
|
||||
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => false,
|
||||
// sub-delims allowed in both path and query
|
||||
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' => false,
|
||||
// Separators allowed in both path and query
|
||||
'/', ':', '@' => false,
|
||||
// Query-specific: '?' is allowed in queries but not in paths
|
||||
'?' => comptime is_path,
|
||||
// sub-delims allowed in path/query but some must be encoded in userinfo
|
||||
'!', '$', '&', '\'', '(', ')', '*', '+', ',' => false,
|
||||
';', '=' => encode_set == .userinfo,
|
||||
// Separators: userinfo must encode these
|
||||
'/', ':', '@' => encode_set == .userinfo,
|
||||
// '?' is allowed in queries but not in paths or userinfo
|
||||
'?' => encode_set != .query,
|
||||
// Everything else needs encoding (including space)
|
||||
else => true,
|
||||
};
|
||||
@@ -514,7 +517,7 @@ pub fn setHost(current: [:0]const u8, value: []const u8, allocator: Allocator) !
|
||||
const search = getSearch(current);
|
||||
const hash = getHash(current);
|
||||
|
||||
// Check if the host includes a port
|
||||
// Check if the new value includes a port
|
||||
const colon_pos = std.mem.lastIndexOfScalar(u8, value, ':');
|
||||
const clean_host = if (colon_pos) |pos| blk: {
|
||||
const port_str = value[pos + 1 ..];
|
||||
@@ -526,7 +529,14 @@ pub fn setHost(current: [:0]const u8, value: []const u8, allocator: Allocator) !
|
||||
break :blk value[0..pos];
|
||||
}
|
||||
break :blk value;
|
||||
} else value;
|
||||
} else blk: {
|
||||
// No port in new value - preserve existing port
|
||||
const current_port = getPort(current);
|
||||
if (current_port.len > 0) {
|
||||
break :blk try std.fmt.allocPrint(allocator, "{s}:{s}", .{ value, current_port });
|
||||
}
|
||||
break :blk value;
|
||||
};
|
||||
|
||||
return buildUrl(allocator, protocol, clean_host, pathname, search, hash);
|
||||
}
|
||||
@@ -544,6 +554,9 @@ pub fn setHostname(current: [:0]const u8, value: []const u8, allocator: Allocato
|
||||
pub fn setPort(current: [:0]const u8, value: ?[]const u8, allocator: Allocator) ![:0]const u8 {
|
||||
const hostname = getHostname(current);
|
||||
const protocol = getProtocol(current);
|
||||
const pathname = getPathname(current);
|
||||
const search = getSearch(current);
|
||||
const hash = getHash(current);
|
||||
|
||||
// Handle null or default ports
|
||||
const new_host = if (value) |port_str| blk: {
|
||||
@@ -560,7 +573,7 @@ pub fn setPort(current: [:0]const u8, value: ?[]const u8, allocator: Allocator)
|
||||
break :blk try std.fmt.allocPrint(allocator, "{s}:{s}", .{ hostname, port_str });
|
||||
} else hostname;
|
||||
|
||||
return setHost(current, new_host, allocator);
|
||||
return buildUrl(allocator, protocol, new_host, pathname, search, hash);
|
||||
}
|
||||
|
||||
pub fn setPathname(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
|
||||
@@ -608,6 +621,64 @@ pub fn setHash(current: [:0]const u8, value: []const u8, allocator: Allocator) !
|
||||
return buildUrl(allocator, protocol, host, pathname, search, hash);
|
||||
}
|
||||
|
||||
pub fn setUsername(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
|
||||
const protocol = getProtocol(current);
|
||||
const host = getHost(current);
|
||||
const pathname = getPathname(current);
|
||||
const search = getSearch(current);
|
||||
const hash = getHash(current);
|
||||
const password = getPassword(current);
|
||||
|
||||
const encoded_username = try percentEncodeSegment(allocator, value, .userinfo);
|
||||
return buildUrlWithUserInfo(allocator, protocol, encoded_username, password, host, pathname, search, hash);
|
||||
}
|
||||
|
||||
pub fn setPassword(current: [:0]const u8, value: []const u8, allocator: Allocator) ![:0]const u8 {
|
||||
const protocol = getProtocol(current);
|
||||
const host = getHost(current);
|
||||
const pathname = getPathname(current);
|
||||
const search = getSearch(current);
|
||||
const hash = getHash(current);
|
||||
const username = getUsername(current);
|
||||
|
||||
const encoded_password = try percentEncodeSegment(allocator, value, .userinfo);
|
||||
return buildUrlWithUserInfo(allocator, protocol, username, encoded_password, host, pathname, search, hash);
|
||||
}
|
||||
|
||||
fn buildUrlWithUserInfo(
|
||||
allocator: Allocator,
|
||||
protocol: []const u8,
|
||||
username: []const u8,
|
||||
password: []const u8,
|
||||
host: []const u8,
|
||||
pathname: []const u8,
|
||||
search: []const u8,
|
||||
hash: []const u8,
|
||||
) ![:0]const u8 {
|
||||
if (username.len == 0 and password.len == 0) {
|
||||
return buildUrl(allocator, protocol, host, pathname, search, hash);
|
||||
} else if (password.len == 0) {
|
||||
return std.fmt.allocPrintSentinel(allocator, "{s}//{s}@{s}{s}{s}{s}", .{
|
||||
protocol,
|
||||
username,
|
||||
host,
|
||||
pathname,
|
||||
search,
|
||||
hash,
|
||||
}, 0);
|
||||
} else {
|
||||
return std.fmt.allocPrintSentinel(allocator, "{s}//{s}:{s}@{s}{s}{s}{s}", .{
|
||||
protocol,
|
||||
username,
|
||||
password,
|
||||
host,
|
||||
pathname,
|
||||
search,
|
||||
hash,
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn concatQueryString(arena: Allocator, url: []const u8, query_string: []const u8) ![:0]const u8 {
|
||||
if (query_string.len == 0) {
|
||||
return arena.dupeZ(u8, url);
|
||||
|
||||
@@ -218,6 +218,106 @@
|
||||
testing.expectEqual('', url.password);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://example.com/path');
|
||||
url.username = 'newuser';
|
||||
testing.expectEqual('newuser', url.username);
|
||||
testing.expectEqual('https://newuser@example.com/path', url.href);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://olduser@example.com/path');
|
||||
url.username = 'newuser';
|
||||
testing.expectEqual('newuser', url.username);
|
||||
testing.expectEqual('https://newuser@example.com/path', url.href);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://olduser:pass@example.com/path');
|
||||
url.username = 'newuser';
|
||||
testing.expectEqual('newuser', url.username);
|
||||
testing.expectEqual('pass', url.password);
|
||||
testing.expectEqual('https://newuser:pass@example.com/path', url.href);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://user@example.com/path');
|
||||
url.password = 'secret';
|
||||
testing.expectEqual('user', url.username);
|
||||
testing.expectEqual('secret', url.password);
|
||||
testing.expectEqual('https://user:secret@example.com/path', url.href);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://user:oldpass@example.com/path');
|
||||
url.password = 'newpass';
|
||||
testing.expectEqual('user', url.username);
|
||||
testing.expectEqual('newpass', url.password);
|
||||
testing.expectEqual('https://user:newpass@example.com/path', url.href);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://user:pass@example.com/path');
|
||||
url.username = '';
|
||||
url.password = '';
|
||||
testing.expectEqual('', url.username);
|
||||
testing.expectEqual('', url.password);
|
||||
testing.expectEqual('https://example.com/path', url.href);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://example.com/path');
|
||||
url.username = 'user@domain';
|
||||
testing.expectEqual('user%40domain', url.username);
|
||||
testing.expectEqual('https://user%40domain@example.com/path', url.href);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://example.com/path');
|
||||
url.username = 'user:name';
|
||||
testing.expectEqual('user%3Aname', url.username);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://example.com/path');
|
||||
url.password = 'pass@word';
|
||||
testing.expectEqual('pass%40word', url.password);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://example.com/path');
|
||||
url.password = 'pass:word';
|
||||
testing.expectEqual('pass%3Aword', url.password);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://example.com/path');
|
||||
url.username = 'user/name';
|
||||
testing.expectEqual('user%2Fname', url.username);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://example.com/path');
|
||||
url.password = 'pass?word';
|
||||
testing.expectEqual('pass%3Fword', url.password);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://user%40domain:pass%3Aword@example.com/path');
|
||||
testing.expectEqual('user%40domain', url.username);
|
||||
testing.expectEqual('pass%3Aword', url.password);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('https://example.com:8080/path?a=b#hash');
|
||||
url.username = 'user';
|
||||
url.password = 'pass';
|
||||
testing.expectEqual('https://user:pass@example.com:8080/path?a=b#hash', url.href);
|
||||
testing.expectEqual('8080', url.port);
|
||||
testing.expectEqual('?a=b', url.search);
|
||||
testing.expectEqual('#hash', url.hash);
|
||||
}
|
||||
|
||||
{
|
||||
const url = new URL('http://user:pass@example.com:8080/path?query=1#hash');
|
||||
testing.expectEqual('http:', url.protocol);
|
||||
@@ -437,9 +537,9 @@
|
||||
{
|
||||
const url = new URL('https://example.com:8080/path');
|
||||
url.host = 'newhost.com';
|
||||
testing.expectEqual('https://newhost.com/path', url.href);
|
||||
testing.expectEqual('https://newhost.com:8080/path', url.href);
|
||||
testing.expectEqual('newhost.com', url.hostname);
|
||||
testing.expectEqual('', url.port);
|
||||
testing.expectEqual('8080', url.port);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -66,10 +66,20 @@ pub fn getUsername(self: *const URL) []const u8 {
|
||||
return U.getUsername(self._raw);
|
||||
}
|
||||
|
||||
pub fn setUsername(self: *URL, value: []const u8) !void {
|
||||
const allocator = self._arena orelse return error.NoAllocator;
|
||||
self._raw = try U.setUsername(self._raw, value, allocator);
|
||||
}
|
||||
|
||||
pub fn getPassword(self: *const URL) []const u8 {
|
||||
return U.getPassword(self._raw);
|
||||
}
|
||||
|
||||
pub fn setPassword(self: *URL, value: []const u8) !void {
|
||||
const allocator = self._arena orelse return error.NoAllocator;
|
||||
self._raw = try U.setPassword(self._raw, value, allocator);
|
||||
}
|
||||
|
||||
pub fn getPathname(self: *const URL) []const u8 {
|
||||
return U.getPathname(self._raw);
|
||||
}
|
||||
@@ -272,8 +282,8 @@ pub const JsApi = struct {
|
||||
pub const search = bridge.accessor(URL.getSearch, URL.setSearch, .{});
|
||||
pub const hash = bridge.accessor(URL.getHash, URL.setHash, .{});
|
||||
pub const pathname = bridge.accessor(URL.getPathname, URL.setPathname, .{});
|
||||
pub const username = bridge.accessor(URL.getUsername, null, .{});
|
||||
pub const password = bridge.accessor(URL.getPassword, null, .{});
|
||||
pub const username = bridge.accessor(URL.getUsername, URL.setUsername, .{});
|
||||
pub const password = bridge.accessor(URL.getPassword, URL.setPassword, .{});
|
||||
pub const hostname = bridge.accessor(URL.getHostname, URL.setHostname, .{});
|
||||
pub const host = bridge.accessor(URL.getHost, URL.setHost, .{});
|
||||
pub const port = bridge.accessor(URL.getPort, URL.setPort, .{});
|
||||
|
||||
Reference in New Issue
Block a user