mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-21 20:24:42 +00:00
Merge pull request #1517 from lightpanda-io/input_type_change
Sanitize input values based on type (and on type change)
This commit is contained in:
@@ -266,6 +266,7 @@ pub const Accessor = struct {
|
|||||||
defer caller.deinit();
|
defer caller.deinit();
|
||||||
|
|
||||||
caller.method(T, setter, handle.?, .{
|
caller.method(T, setter, handle.?, .{
|
||||||
|
.dom_exception = opts.dom_exception,
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -108,8 +108,13 @@ pub fn getValue(self: *const Input) []const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setValue(self: *Input, value: []const u8, page: *Page) !void {
|
pub fn setValue(self: *Input, value: []const u8, page: *Page) !void {
|
||||||
// This should _not_ call setAttribute. It updates the default state only
|
// File inputs cannot have their value set programmatically for security reasons
|
||||||
const owned = try page.dupeString(value);
|
if (self._input_type == .file) {
|
||||||
|
return error.InvalidStateError;
|
||||||
|
}
|
||||||
|
// This should _not_ call setAttribute. It updates the current state only
|
||||||
|
const sanitized = try self.sanitizeValue(value, page);
|
||||||
|
const owned = try page.dupeString(sanitized);
|
||||||
self._value = owned;
|
self._value = owned;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +406,53 @@ pub fn getForm(self: *Input, page: *Page) ?*Form {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sanitize the value according to the current input type
|
||||||
|
fn sanitizeValue(self: *Input, value: []const u8, page: *Page) ![]const u8 {
|
||||||
|
switch (self._input_type) {
|
||||||
|
.text, .search, .tel, .password, .url, .email => {
|
||||||
|
var i: usize = 0;
|
||||||
|
const result = try page.call_arena.alloc(u8, value.len);
|
||||||
|
for (value) |c| {
|
||||||
|
if (c != '\r' and c != '\n') {
|
||||||
|
result[i] = c;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sanitized = result[0..i];
|
||||||
|
return switch (self._input_type) {
|
||||||
|
.url, .email => std.mem.trim(u8, sanitized, &std.ascii.whitespace),
|
||||||
|
else => sanitized,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.date, .time, .@"datetime-local", .month, .week => {
|
||||||
|
// TODO, we should sanitize this, but lack the necessary functions
|
||||||
|
// datetime.zig could handle date and time, but not the other three
|
||||||
|
// for now, allow al values.
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
.number => {
|
||||||
|
_ = std.fmt.parseFloat(f64, value) catch return "";
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
.range => {
|
||||||
|
// Range: default to "50" if invalid
|
||||||
|
_ = std.fmt.parseFloat(f64, value) catch return "50";
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
.color => {
|
||||||
|
if (value.len == 7 and value[0] == '#') {
|
||||||
|
for (value[1..]) |c| {
|
||||||
|
if (!std.ascii.isHex(c)) return "#000000";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return "#000000";
|
||||||
|
},
|
||||||
|
.file => return "", // File: always empty
|
||||||
|
.checkbox, .radio, .submit, .image, .reset, .button, .hidden => return value, // no sanitization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn uncheckRadioGroup(self: *Input, page: *Page) !void {
|
fn uncheckRadioGroup(self: *Input, page: *Page) !void {
|
||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
|
|
||||||
@@ -454,7 +506,7 @@ pub const JsApi = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const @"type" = bridge.accessor(Input.getType, Input.setType, .{});
|
pub const @"type" = bridge.accessor(Input.getType, Input.setType, .{});
|
||||||
pub const value = bridge.accessor(Input.getValue, Input.setValue, .{});
|
pub const value = bridge.accessor(Input.getValue, Input.setValue, .{ .dom_exception = true });
|
||||||
pub const defaultValue = bridge.accessor(Input.getDefaultValue, Input.setDefaultValue, .{});
|
pub const defaultValue = bridge.accessor(Input.getDefaultValue, Input.setDefaultValue, .{});
|
||||||
pub const checked = bridge.accessor(Input.getChecked, Input.setChecked, .{});
|
pub const checked = bridge.accessor(Input.getChecked, Input.setChecked, .{});
|
||||||
pub const defaultChecked = bridge.accessor(Input.getDefaultChecked, Input.setDefaultChecked, .{});
|
pub const defaultChecked = bridge.accessor(Input.getDefaultChecked, Input.setDefaultChecked, .{});
|
||||||
@@ -505,7 +557,17 @@ pub const Build = struct {
|
|||||||
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name.str()) orelse return;
|
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name.str()) orelse return;
|
||||||
const self = element.as(Input);
|
const self = element.as(Input);
|
||||||
switch (attribute) {
|
switch (attribute) {
|
||||||
.type => self._input_type = Type.fromString(value.str()),
|
.type => {
|
||||||
|
self._input_type = Type.fromString(value.str());
|
||||||
|
// Sanitize the current value according to the new type
|
||||||
|
if (self._value) |current_value| {
|
||||||
|
self._value = try self.sanitizeValue(current_value, page);
|
||||||
|
// Apply default value for checkbox/radio if value is now empty
|
||||||
|
if (self._value.?.len == 0 and (self._input_type == .checkbox or self._input_type == .radio)) {
|
||||||
|
self._value = "on";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
.value => self._default_value = try page.arena.dupe(u8, value.str()),
|
.value => self._default_value = try page.arena.dupe(u8, value.str()),
|
||||||
.checked => {
|
.checked => {
|
||||||
self._default_checked = true;
|
self._default_checked = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user