diff options
19 files changed, 3701 insertions, 22 deletions
diff --git a/src/deps/picohttp.zig b/src/deps/picohttp.zig index d822b5d73..14e4c9f7c 100644 --- a/src/deps/picohttp.zig +++ b/src/deps/picohttp.zig @@ -171,6 +171,12 @@ test "pico_http: parse response" { pub const Headers = struct { headers: []const Header, + pub fn format(self: Headers, comptime _: []const u8, _: fmt.FormatOptions, writer: anytype) !void { + for (self.headers) |header| { + try fmt.format(writer, "{s}: {s}\r\n", .{ header.name, header.value }); + } + } + pub fn parse(buf: []const u8, src: []Header) !Headers { var num_headers: usize = src.len; diff --git a/src/deps/uws.zig b/src/deps/uws.zig index ae0efef79..1fbce62c5 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -8,6 +8,205 @@ pub const u_int64_t = c_ulonglong; pub const LIBUS_LISTEN_DEFAULT: c_int = 0; pub const LIBUS_LISTEN_EXCLUSIVE_PORT: c_int = 1; pub const Socket = opaque {}; +const bun = @import("../global.zig"); + +pub fn NewSocketHandler(comptime ssl: bool) type { + return struct { + const ssl_int: c_int = @boolToInt(ssl); + socket: *Socket, + const ThisSocket = @This(); + + pub fn isEstablished(this: ThisSocket) bool { + return us_socket_is_established(comptime ssl_int, this.socket); + } + + pub fn timeout(this: ThisSocket, seconds: c_uint) void { + return us_socket_timeout(comptime ssl_int, this.socket, seconds); + } + pub fn ext(this: ThisSocket, comptime ContextType: type) ?*ContextType { + var ptr = us_socket_ext( + comptime ssl_int, + this.socket, + ) orelse return null; + + return @ptrCast(*ContextType, @alignCast(std.meta.alignment(ContextType), ptr)); + } + pub fn context(this: ThisSocket) *us_socket_context_t { + return us_socket_context( + comptime ssl_int, + this.socket, + ); + } + pub fn flush(this: ThisSocket) void { + return us_socket_flush( + comptime ssl_int, + this.socket, + ); + } + pub fn write(this: ThisSocket, data: []const u8, msg_more: bool) c_int { + return us_socket_write( + comptime ssl_int, + this.socket, + data.ptr, + data.len, + @as(c_int, @boolToInt(msg_more)), + ); + } + pub fn shutdown(this: ThisSocket) void { + return us_socket_shutdown( + comptime ssl_int, + this.socket, + ); + } + pub fn shutdownRead(this: ThisSocket) void { + return us_socket_shutdown_read( + comptime ssl_int, + this.socket, + ); + } + pub fn isShutdown(this: ThisSocket) c_int { + return us_socket_is_shut_down( + comptime ssl_int, + this.socket, + ); + } + pub fn isClosed(this: ThisSocket) c_int { + return us_socket_is_closed( + comptime ssl_int, + this.socket, + ); + } + pub fn close(this: ThisSocket, code: c_int, reason: ?*anyopaque) void { + return us_socket_close( + comptime ssl_int, + this.socket, + code, + reason, + ); + } + pub fn localPort(this: ThisSocket) c_int { + return us_socket_local_port( + comptime ssl_int, + this.socket, + ); + } + pub fn remoteAddress(this: ThisSocket, buf: [*]u8, length: [*c]c_int) void { + return us_socket_remote_address( + comptime ssl_int, + this.socket, + buf, + length, + ); + } + + pub fn connect( + host: []const u8, + port: c_int, + socket_ctx: *us_socket_context_t, + comptime Context: type, + ctx: Context, + comptime socket_field_name: []const u8, + ) ?*Context { + var stack_fallback = std.heap.stackFallback(1024, bun.default_allocator); + var allocator = stack_fallback.get(); + var host_ = allocator.dupeZ(u8, host) orelse return null; + defer allocator.free(host_); + + var socket = us_socket_context_connect(comptime ssl_int, socket_ctx, host_, port, null, 0, @sizeOf(Context)) orelse return null; + const socket_ = ThisSocket{ .socket = socket }; + var holder = socket_.ext(Context) orelse { + if (comptime bun.Environment.allow_assert) unreachable; + _ = us_socket_close_connecting(comptime ssl_int, socket); + return null; + }; + holder.* = ctx; + @field(holder, socket_field_name) = socket_; + return holder; + } + + pub fn configure( + ctx: *us_socket_context_t, + comptime ContextType: type, + comptime onOpen: anytype, + comptime onClose: anytype, + comptime onData: anytype, + comptime onWritable: anytype, + comptime onTimeout: anytype, + comptime onConnectError: anytype, + comptime onEnd: anytype, + ) void { + const SocketHandler = struct { + pub fn on_open(socket: *Socket, _: c_int, ip: [*:0]u8, port: c_int) callconv(.C) ?*Socket { + onOpen( + @ptrCast(*ContextType, @alignCast(std.meta.alignment(ContextType), us_socket_ext(comptime ssl_int, socket).?)), + socket, + bun.span(ip), + port, + ); + return socket; + } + pub fn on_close(socket: *Socket, code: c_int, reason: ?*anyopaque) callconv(.C) ?*Socket { + onClose( + @ptrCast(*ContextType, @alignCast(std.meta.alignment(ContextType), us_socket_ext(comptime ssl_int, socket).?)), + socket, + code, + reason, + ); + return socket; + } + pub fn on_data(socket: *Socket, buf: ?[*]u8, len: c_int) callconv(.C) ?*Socket { + onData( + @ptrCast(*ContextType, @alignCast(std.meta.alignment(ContextType), us_socket_ext(comptime ssl_int, socket).?)), + socket, + buf.?[0..len], + ); + return socket; + } + pub fn on_writable(socket: *Socket) callconv(.C) ?*Socket { + onWritable( + @ptrCast(*ContextType, @alignCast(std.meta.alignment(ContextType), us_socket_ext(comptime ssl_int, socket).?)), + socket, + ); + return socket; + } + pub fn on_timeout(socket: *Socket) callconv(.C) ?*Socket { + onTimeout( + @ptrCast(*ContextType, @alignCast(std.meta.alignment(ContextType), us_socket_ext(comptime ssl_int, socket).?)), + socket, + ); + return socket; + } + pub fn on_connect_error(socket: *Socket, code: c_int) callconv(.C) ?*Socket { + onConnectError( + @ptrCast(*ContextType, @alignCast(std.meta.alignment(ContextType), us_socket_ext(comptime ssl_int, socket).?)), + socket, + code, + ); + return socket; + } + pub fn on_end(socket: *Socket) callconv(.C) ?*Socket { + onEnd( + @ptrCast(*ContextType, @alignCast(std.meta.alignment(ContextType), us_socket_ext(comptime ssl_int, socket).?)), + socket, + ); + return socket; + } + }; + + us_socket_context_on_open(ssl_int, ctx, SocketHandler.on_open); + us_socket_context_on_close(ssl_int, ctx, SocketHandler.on_close); + us_socket_context_on_data(ssl_int, ctx, SocketHandler.on_data); + us_socket_context_on_writable(ssl_int, ctx, SocketHandler.on_writable); + us_socket_context_on_timeout(ssl_int, ctx, SocketHandler.on_timeout); + us_socket_context_on_connect_error(ssl_int, ctx, SocketHandler.on_connect_error); + us_socket_context_on_end(ssl_int, ctx, SocketHandler.on_end); + } + }; +} + +pub const SocketTCP = NewSocketHandler(false); +pub const SocketTLS = NewSocketHandler(true); + pub const us_timer_t = opaque {}; pub const us_socket_context_t = opaque {}; pub const Loop = opaque { @@ -90,13 +289,13 @@ extern fn us_socket_context_on_server_name(ssl: c_int, context: ?*us_socket_cont extern fn us_socket_context_get_native_handle(ssl: c_int, context: ?*us_socket_context_t) ?*anyopaque; extern fn us_create_socket_context(ssl: c_int, loop: ?*Loop, ext_size: c_int, options: us_socket_context_options_t) ?*us_socket_context_t; extern fn us_socket_context_free(ssl: c_int, context: ?*us_socket_context_t) void; -extern fn us_socket_context_on_open(ssl: c_int, context: ?*us_socket_context_t, on_open: ?fn (?*Socket, c_int, [*c]u8, c_int) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_close(ssl: c_int, context: ?*us_socket_context_t, on_close: ?fn (?*Socket, c_int, ?*anyopaque) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_data(ssl: c_int, context: ?*us_socket_context_t, on_data: ?fn (?*Socket, [*c]u8, c_int) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_writable(ssl: c_int, context: ?*us_socket_context_t, on_writable: ?fn (*Socket) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_timeout(ssl: c_int, context: ?*us_socket_context_t, on_timeout: ?fn (?*Socket) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_connect_error(ssl: c_int, context: ?*us_socket_context_t, on_connect_error: ?fn (?*Socket, c_int) callconv(.C) ?*Socket) void; -extern fn us_socket_context_on_end(ssl: c_int, context: ?*us_socket_context_t, on_end: ?fn (?*Socket) callconv(.C) ?*Socket) void; +extern fn us_socket_context_on_open(ssl: c_int, context: ?*us_socket_context_t, on_open: fn (?*Socket, c_int, [*c]u8, c_int) callconv(.C) ?*Socket) void; +extern fn us_socket_context_on_close(ssl: c_int, context: ?*us_socket_context_t, on_close: fn (?*Socket, c_int, ?*anyopaque) callconv(.C) ?*Socket) void; +extern fn us_socket_context_on_data(ssl: c_int, context: ?*us_socket_context_t, on_data: fn (?*Socket, [*c]u8, c_int) callconv(.C) ?*Socket) void; +extern fn us_socket_context_on_writable(ssl: c_int, context: ?*us_socket_context_t, on_writable: fn (*Socket) callconv(.C) ?*Socket) void; +extern fn us_socket_context_on_timeout(ssl: c_int, context: ?*us_socket_context_t, on_timeout: fn (?*Socket) callconv(.C) ?*Socket) void; +extern fn us_socket_context_on_connect_error(ssl: c_int, context: ?*us_socket_context_t, on_connect_error: fn (?*Socket, c_int) callconv(.C) ?*Socket) void; +extern fn us_socket_context_on_end(ssl: c_int, context: ?*us_socket_context_t, on_end: fn (?*Socket) callconv(.C) ?*Socket) void; extern fn us_socket_context_ext(ssl: c_int, context: ?*us_socket_context_t) ?*anyopaque; extern fn us_socket_context_listen(ssl: c_int, context: ?*us_socket_context_t, host: [*c]const u8, port: c_int, options: c_int, socket_ext_size: c_int) ?*listen_socket_t; @@ -183,11 +382,12 @@ pub const Poll = opaque { }; extern fn us_socket_get_native_handle(ssl: c_int, s: ?*Socket) ?*anyopaque; -extern fn us_socket_write(ssl: c_int, s: ?*Socket, data: [*c]const u8, length: c_int, msg_more: c_int) c_int; -extern fn Socketimeout(ssl: c_int, s: ?*Socket, seconds: c_uint) void; + +extern fn us_socket_timeout(ssl: c_int, s: ?*Socket, seconds: c_uint) void; extern fn us_socket_ext(ssl: c_int, s: ?*Socket) ?*anyopaque; extern fn us_socket_context(ssl: c_int, s: ?*Socket) ?*us_socket_context_t; extern fn us_socket_flush(ssl: c_int, s: ?*Socket) void; +extern fn us_socket_write(ssl: c_int, s: ?*Socket, data: [*c]const u8, length: c_int, msg_more: c_int) c_int; extern fn us_socket_shutdown(ssl: c_int, s: ?*Socket) void; extern fn us_socket_shutdown_read(ssl: c_int, s: ?*Socket) void; extern fn us_socket_is_shut_down(ssl: c_int, s: ?*Socket) c_int; diff --git a/src/http/websocket_http_client.zig b/src/http/websocket_http_client.zig new file mode 100644 index 000000000..5e795bb4a --- /dev/null +++ b/src/http/websocket_http_client.zig @@ -0,0 +1,451 @@ +// This code is based on https://github.com/frmdstryr/zhp/blob/a4b5700c289c3619647206144e10fb414113a888/src/websocket.zig +// Thank you @frmdstryr. +const std = @import("std"); +const native_endian = @import("builtin").target.cpu.arch.endian(); + +const tcp = std.x.net.tcp; +const ip = std.x.net.ip; + +const IPv4 = std.x.os.IPv4; +const IPv6 = std.x.os.IPv6; +const os = std.os; +const bun = @import("../global.zig"); +const string = bun.string; +const Output = bun.Output; +const Global = bun.Global; +const Environment = bun.Environment; +const strings = bun.strings; +const MutableString = bun.MutableString; +const stringZ = bun.stringZ; +const default_allocator = bun.default_allocator; +const C = bun.C; + +const uws = @import("uws"); +const JSC = @import("javascript_core"); +const PicoHTTP = @import("picohttp"); +const ObjectPool = @import("../pool.zig").ObjectPool; + +fn buildRequestBody(vm: *JSC.VirtualMachine, pathname: *const JSC.ZigString, host: *const JSC.ZigString, client_protocol: *const JSC.ZigString, client_protocol_hash: *u64) ![]u8 { + const allocator = vm.allocator(); + var input_rand_buf: [16]u8 = undefined; + std.crypto.random.bytes(&input_rand_buf); + const temp_buf_size = comptime std.base64.standard.Encoder.calcSize(16); + var encoded_buf: [temp_buf_size]u8 = undefined; + const accept_key = std.base64.standard.Encoder.encode(&encoded_buf, &input_rand_buf); + + var headers = [_]PicoHTTP.Header{ + .{ + .name = "Sec-WebSocket-Key", + .value = accept_key, + }, + .{ + .name = "Sec-WebSocket-Protocol", + .value = client_protocol.slice(), + }, + }; + + if (client_protocol.len > 0) + client_protocol_hash.* = std.hash.Wyhash.hash(0, headers[1].value); + + var headers_: []PicoHTTP.Header = headers[0 .. 1 + @as(usize, @boolToInt(client_protocol.len > 0))]; + + return try std.fmt.allocPrint(allocator, + \\GET {} HTTP/1.1\r + \\Host: {}\r + \\Pragma: no-cache\r + \\Cache-Control: no-cache\r + \\Connection: Upgrade\r + \\Upgrade: websocket\r + \\Sec-WebSocket-Version: 13\r + \\{s} + \\\r + \\ + , .{ + pathname.*, + host.*, + PicoHTTP.Headers{ .headers = headers_ }, + }); +} + +const ErrorCode = enum(i32) { + cancel, + invalid_response, + expected_101_status_code, + missing_upgrade_header, + missing_connection_header, + missing_websocket_accept_header, + invalid_upgrade_header, + invalid_connection_header, + invalid_websocket_version, + mismatch_websocket_accept_header, + missing_client_protocol, + mismatch_client_protocol, + timeout, + closed, + failed_to_write, + failed_to_connect, + headers_too_large, + ended, +}; +extern fn WebSocket__didConnect( + websocket_context: *anyopaque, + socket: *uws.Socket, + buffered_data: ?[*]u8, + buffered_len: usize, +) void; +extern fn WebSocket__didFailToConnect(websocket_context: *anyopaque, reason: ErrorCode) void; + +const BodyBufBytes = [16384 - 16]u8; + +const BodyBufPool = ObjectPool(BodyBufBytes, null, true, 4); +const BodyBuf = BodyBufPool.Node; + +pub fn NewHTTPUpgradeClient(comptime ssl: bool) type { + return struct { + pub const Socket = uws.NewSocketHandler(ssl); + socket: Socket, + outgoing_websocket: *anyopaque, + input_body_buf: []u8 = &[_]u8{}, + client_protocol: []const u8 = "", + to_send: []const u8 = "", + read_length: usize = 0, + headers_buf: [128]PicoHTTP.Header = undefined, + body_buf: ?*BodyBuf = null, + body_written: usize = 0, + websocket_protocol: u64 = 0, + + const basename = (if (ssl) "SecureWebSocket" else "WebSocket") ++ "UpgradeClient"; + + pub const shim = JSC.Shimmer("Bun", basename, @This()); + + const HTTPClient = @This(); + + pub fn register(global: *JSC.JSGlobalObject, loop_: *anyopaque, ctx_: *anyopaque) void { + var vm = global.bunVM(); + var loop = @ptrCast(*uws.Loop, loop_); + var ctx: *uws.us_socket_context_t = @ptrCast(*uws.us_socket_context_t, ctx_); + + if (vm.uws_event_loop) |other| { + std.debug.assert(other == loop); + } + + vm.uws_event_loop = loop; + + Socket.configure(ctx, HTTPClient, handleOpen, handleClose, handleData, handleWritable, handleTimeout, handleConnectError, handleEnd); + } + + pub fn connect( + global: *JSC.JSGlobalObject, + socket_ctx: *anyopaque, + websocket: *anyopaque, + host: *const JSC.ZigString, + port: u16, + pathname: *const JSC.ZigString, + client_protocol: *const JSC.ZigString, + outgoing_usocket: **anyopaque, + ) ?*HTTPClient { + std.debug.assert(global.bunVM().uws_event_loop != null); + + var client_protocol_hash: u64 = 0; + var body = buildRequestBody(global.bunVM(), pathname, host, client_protocol, &client_protocol_hash) catch return null; + var client: HTTPClient = HTTPClient{ + .socket = undefined, + .outgoing_websocket = websocket, + .input_body_buf = body, + .websocket_protocol = client_protocol_hash, + }; + var host_ = host.toSlice(bun.default_allocator); + defer host_.deinit(); + + if (Socket.connect(host_.slice(), port, @ptrCast(*uws.us_socket_context_t, socket_ctx), HTTPClient, client, "socket")) |out| { + outgoing_usocket.* = out.socket.socket; + out.socket.timeout(120); + return out; + } + + client.clearData(); + + return null; + } + + pub fn clearInput(this: *HTTPClient) void { + if (this.input_body_buf.len > 0) bun.default_allocator.free(this.input_body_buf); + this.input_body_buf.len = 0; + } + pub fn clearData(this: *HTTPClient) void { + this.clearInput(); + if (this.body_buf) |buf| { + this.body_buf = null; + buf.release(); + } + } + pub fn cancel(this: *HTTPClient) void { + this.clearData(); + + if (!this.socket.isEstablished()) { + _ = uws.us_socket_close_connecting(comptime @as(c_int, @boolToInt(ssl)), this.socket); + } else { + this.socket.close(0, null); + } + } + + pub fn fail(this: *HTTPClient, code: ErrorCode) void { + WebSocket__didFailToConnect(this.outgoing_websocket, code); + this.cancel(); + } + + pub fn handleClose(this: *HTTPClient, _: Socket, _: c_int, _: ?*anyopaque) void { + this.clearData(); + WebSocket__didFailToConnect(this.outgoing_websocket, ErrorCode.closed); + } + + pub fn terminate(this: *HTTPClient, code: ErrorCode) void { + this.fail(code); + if (this.socket.isClosed() == 0) + this.socket.close(0, null); + } + + pub fn handleOpen(this: *HTTPClient, socket: Socket) void { + std.debug.assert(socket.socket == this.socket.socket); + + std.debug.assert(this.input_body_buf.len > 0); + std.debug.assert(this.to_send.len == 0); + + const wrote = socket.write(this.input_body_buf, false); + if (wrote < 0) { + this.terminate(ErrorCode.failed_to_write); + return; + } + + this.to_send = this.input_body_buf[@intCast(usize, wrote)..]; + } + + fn getBody(this: *HTTPClient) *BodyBufBytes { + if (this.body_buf == null) { + this.body_buf = BodyBufPool.get(bun.default_allocator); + } + + return &this.body_buf.?.data; + } + + pub fn handleData(this: *HTTPClient, socket: Socket, data: []const u8) void { + std.debug.assert(socket.socket == this.socket.socket); + + if (comptime Environment.allow_assert) + std.debug.assert(socket.isShutdown() == 0); + + var body = this.getBody(); + var remain = body[this.body_written..]; + const is_first = this.body_written == 0; + if (is_first and data.len >= "101 ".len) { + // fail early if we receive a non-101 status code + if (!strings.eqlComptimeIgnoreLen(data[0.."101 ".len], "101 ")) { + this.terminate(ErrorCode.expected_101_status_code); + return; + } + } + + const to_write = remain[0..@minimum(remain.len, data.len)]; + if (data.len > 0 and to_write.len > 0) { + @memcpy(remain.ptr, data, to_write.len); + this.body_written += to_write.len; + } + + const overflow = data[to_write.len..]; + + const available_to_read = body[0..this.body_written]; + const response = PicoHTTP.Response.parse(available_to_read, &this.headers_buf) catch |err| { + switch (err) { + error.Malformed_HTTP_Response => { + this.terminate(ErrorCode.invalid_response); + return; + }, + error.ShortRead => { + if (overflow.len > 0) { + this.terminate(ErrorCode.headers_too_large); + return; + } + return; + }, + } + }; + + var buffered_body_data = body[@minimum(@intCast(usize, response.bytes_read), body.len)..]; + buffered_body_data = buffered_body_data[0..@minimum(buffered_body_data.len, this.body_written)]; + + this.processResponse(response, buffered_body_data, overflow); + } + + pub fn handleEnd(this: *HTTPClient, socket: Socket) void { + std.debug.assert(socket.socket == this.socket.socket); + this.terminate(ErrorCode.ended); + } + + pub fn processResponse(this: *HTTPClient, response: PicoHTTP.Response, remain_buf: []const u8, overflow_buf: []const u8) void { + std.debug.assert(this.body_written > 0); + + var upgrade_header = PicoHTTP.Header{ .name = "", .value = "" }; + var connection_header = PicoHTTP.Header{ .name = "", .value = "" }; + var websocket_accept_header = PicoHTTP.Header{ .name = "", .value = "" }; + var visited_protocol = this.websocket_protocol == 0; + var visited_version = false; + + if (remain_buf.len > 0) { + std.debug.assert(overflow_buf.len == 0); + } + + for (response.headers) |header| { + switch (header.name.len) { + "Connection".len => { + if (connection_header.name.len == 0 and strings.eqlCaseInsensitiveASCII(header.name, "Connection", false)) { + connection_header = header; + if (visited_protocol and upgrade_header.len > 0 and connection_header.len > 0 and websocket_accept_header.len > 0 and visited_version) { + break; + } + } + }, + "Upgrade".len => { + if (upgrade_header.name.len == 0 and strings.eqlCaseInsensitiveASCII(header.name, "Upgrade", false)) { + upgrade_header = header; + if (visited_protocol and upgrade_header.len > 0 and connection_header.len > 0 and websocket_accept_header.len > 0 and visited_version) { + break; + } + } + }, + "Sec-WebSocket-Version".len => { + if (!visited_version and strings.eqlCaseInsensitiveASCII(header.name, "Sec-WebSocket-Version", false)) { + visited_version = true; + if (!strings.eqlComptime(header.value, "13", false)) { + this.terminate(ErrorCode.invalid_websocket_version); + return; + } + } + }, + "Sec-WebSocket-Accept".len => { + if (websocket_accept_header.name.len == 0 and strings.eqlCaseInsensitiveASCII(header.name, "Sec-WebSocket-Accept", false)) { + websocket_accept_header = header; + if (visited_protocol and upgrade_header.len > 0 and connection_header.len > 0 and websocket_accept_header.len > 0 and visited_version) { + break; + } + } + }, + "Sec-WebSocket-Protocol".len => { + if (strings.eqlCaseInsensitiveASCII(header.name, "Sec-WebSocket-Protocol", false)) { + if (this.websocket_protocol == 0 or std.hash.Wyhash.hash(0, header.value) != this.websocket_protocol) { + this.terminate(ErrorCode.mismatch_client_protocol); + return; + } + visited_protocol = true; + + if (visited_protocol and upgrade_header.len > 0 and connection_header.len > 0 and websocket_accept_header.len > 0 and visited_version) { + break; + } + } + }, + else => {}, + } + } + + if (!visited_version) { + this.terminate(ErrorCode.invalid_websocket_version); + return; + } + + if (@minimum(upgrade_header.name.len, upgrade_header.value.len) == 0) { + this.terminate(ErrorCode.missing_upgrade_header); + return; + } + + if (@minimum(connection_header.name.len, connection_header.value.len) == 0) { + this.terminate(ErrorCode.missing_connection_header); + return; + } + + if (@minimum(websocket_accept_header.name.len, websocket_accept_header.value.len) == 0) { + this.terminate(ErrorCode.missing_websocket_accept_header); + return; + } + + if (!visited_protocol) { + this.terminate(ErrorCode.mismatch_client_protocol); + return; + } + + if (strings.eqlComptime(connection_header.value, "Upgrade")) { + this.terminate(ErrorCode.invalid_connection_header); + return; + } + + if (!strings.eqlComptime(upgrade_header.value, "websocket")) { + this.terminate(ErrorCode.invalid_upgrade_header); + return; + } + + // TODO: check websocket_accept_header.value + + const overflow_len = overflow_buf.len + remain_buf.len; + var overflow: []u8 = &.{}; + if (overflow_len > 0) { + overflow = bun.default_allocator.alloc(u8, overflow_len) catch { + this.terminate(ErrorCode.invalid_response); + return; + }; + if (remain_buf.len > 0) @memcpy(overflow.ptr, remain_buf.ptr, remain_buf.len); + if (overflow_buf.len > 0) @memcpy(overflow.ptr + remain_buf.len, overflow_buf.ptr, remain_buf.len); + } + + this.clearData(); + WebSocket__didConnect(this.outgoing_websocket, this.socket.socket, overflow.ptr, overflow.len); + } + + pub fn handleWritable( + this: *HTTPClient, + socket: Socket, + ) void { + std.debug.assert(socket.socket == this.socket.socket); + + if (this.to_send.len == 0) + return; + + const wrote = socket.write(this.to_send, false); + if (wrote < 0) { + this.terminate(ErrorCode.failed_to_write); + return; + } + std.debug.assert(@intCast(usize, wrote) >= this.to_send.len); + this.to_send = this.to_send[@minimum(@intCast(usize, wrote), this.to_send.len)..]; + } + pub fn handleTimeout( + this: *HTTPClient, + _: Socket, + ) void { + this.terminate(ErrorCode.timeout); + } + pub fn handleConnectError(this: *HTTPClient, _: Socket, _: c_int) void { + this.terminate(ErrorCode.failed_to_connect); + } + + const Exports = shim.exportFunctions(.{ + .connect = connect, + .cancel = cancel, + .register = register, + }); + + comptime { + if (!JSC.is_bindgen) { + @export(connect, .{ + .name = Exports[0].symbol_name, + }); + @export(cancel, .{ + .name = Exports[1].symbol_name, + }); + @export(register, .{ + .name = Exports[2].symbol_name, + }); + } + } + }; +} + +pub const WebSocketUpgradeClient = NewHTTPUpgradeClient(false); +pub const SecureWebSocketUpgradeClient = NewHTTPUpgradeClient(true); diff --git a/src/javascript/jsc/api/html_rewriter.zig b/src/javascript/jsc/api/html_rewriter.zig index 7605cfef7..302af6aac 100644 --- a/src/javascript/jsc/api/html_rewriter.zig +++ b/src/javascript/jsc/api/html_rewriter.zig @@ -32,7 +32,7 @@ const Response = WebCore.Response; const LOLHTML = @import("lolhtml"); const SelectorMap = std.ArrayListUnmanaged(*LOLHTML.HTMLSelector); -const LOLHTMLContext = struct { +pub const LOLHTMLContext = struct { selectors: SelectorMap = .{}, element_handlers: std.ArrayListUnmanaged(*ElementHandler) = .{}, document_handlers: std.ArrayListUnmanaged(*DocumentHandler) = .{}, @@ -232,6 +232,163 @@ pub const HTMLRewriter = struct { return this.beginTransform(global, response); } + pub const HTMLRewriterLoader = struct { + rewriter: *LOLHTML.HTMLRewriter, + finalized: bool = false, + context: LOLHTMLContext, + chunk_size: usize = 0, + failed: bool = false, + output: JSC.WebCore.Sink, + signal: JSC.WebCore.Signal = .{}, + backpressure: std.fifo.LinearFifo(u8, .Dynamic) = std.fifo.LinearFifo(u8, .Dynamic).init(bun.default_allocator), + + pub fn finalize(this: *HTMLRewriterLoader) void { + if (this.finalized) return; + this.rewriter.deinit(); + this.backpressure.deinit(); + this.backpressure = std.fifo.LinearFifo(u8, .Dynamic).init(bun.default_allocator); + this.finalized = true; + } + + pub fn fail(this: *HTMLRewriterLoader, err: JSC.Node.Syscall.Error) void { + this.signal.close(err); + this.output.end(err); + this.failed = true; + this.finalize(); + } + + pub fn connect(this: *HTMLRewriterLoader, signal: JSC.WebCore.Signal) void { + this.signal = signal; + } + + pub fn writeToDestination(this: *HTMLRewriterLoader, bytes: []const u8) void { + if (this.backpressure.count > 0) { + this.backpressure.write(bytes) catch { + this.fail(JSC.Node.Syscall.Error.oom); + this.finalize(); + }; + return; + } + + const write_result = this.output.write(.{ .temporary = bun.ByteList.init(bytes) }); + + switch (write_result) { + .err => |err| { + this.fail(err); + }, + .owned_and_done, .temporary_and_done, .into_array_and_done => { + this.done(); + }, + .pending => |pending| { + pending.applyBackpressure(bun.default_allocator, &this.output, pending, bytes); + }, + .into_array, .owned, .temporary => { + this.signal.ready(if (this.chunk_size > 0) this.chunk_size else null, null); + }, + } + } + + pub fn done( + this: *HTMLRewriterLoader, + ) void { + this.output.end(null); + this.signal.close(null); + this.finalize(); + } + + pub fn setup( + this: *HTMLRewriterLoader, + builder: *LOLHTML.HTMLRewriter.Builder, + context: LOLHTMLContext, + size_hint: ?usize, + output: JSC.WebCore.Sink, + ) ?[]const u8 { + for (context.document_handlers.items) |doc| { + doc.ctx = this; + } + for (context.element_handlers.items) |doc| { + doc.ctx = this; + } + + const chunk_size = @maximum(size_hint orelse 16384, 1024); + this.rewriter = builder.build( + .UTF8, + .{ + .preallocated_parsing_buffer_size = chunk_size, + .max_allowed_memory_usage = std.math.maxInt(u32), + }, + false, + HTMLRewriterLoader, + this, + HTMLRewriterLoader.writeToDestination, + HTMLRewriterLoader.done, + ) catch { + output.end(); + return LOLHTML.HTMLString.lastError().slice(); + }; + + this.chunk_size = chunk_size; + this.context = context; + this.output = output; + + return null; + } + + pub fn sink(this: *HTMLRewriterLoader) JSC.WebCore.Sink { + return JSC.WebCore.Sink.init(this); + } + + fn writeBytes(this: *HTMLRewriterLoader, bytes: bun.ByteList, comptime deinit_: bool) ?JSC.Node.Syscall.Error { + this.rewriter.write(bytes.slice()) catch { + return JSC.Node.Syscall.Error{ + .errno = 1, + // TODO: make this a union + .path = bun.default_allocator.dupe(u8, LOLHTML.HTMLString.lastError().slice()) catch unreachable, + }; + }; + if (comptime deinit_) bytes.listManaged(bun.default_allocator).deinit(); + return null; + } + + pub fn write(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable { + switch (data) { + .owned => |bytes| { + if (this.writeBytes(bytes, true)) |err| { + return .{ .err = err }; + } + return .{ .owned = bytes.len }; + }, + .owned_and_done => |bytes| { + if (this.writeBytes(bytes, true)) |err| { + return .{ .err = err }; + } + return .{ .owned_and_done = bytes.len }; + }, + .temporary_and_done => |bytes| { + if (this.writeBytes(bytes, false)) |err| { + return .{ .err = err }; + } + return .{ .temporary_and_done = bytes.len }; + }, + .temporary => |bytes| { + if (this.writeBytes(bytes, false)) |err| { + return .{ .err = err }; + } + return .{ .temporary = bytes.len }; + }, + else => unreachable, + } + } + + pub fn writeUTF16(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable { + return JSC.WebCore.Sink.UTF8Fallback.writeUTF16(HTMLRewriterLoader, this, data, write); + } + + pub fn writeLatin1(this: *HTMLRewriterLoader, data: JSC.WebCore.StreamResult) JSC.WebCore.StreamResult.Writable { + return JSC.WebCore.Sink.UTF8Fallback.writeLatin1(HTMLRewriterLoader, this, data, write); + } + }; + pub const BufferOutputSink = struct { global: *JSGlobalObject, bytes: bun.MutableString, diff --git a/src/javascript/jsc/bindings/BunJSCModule.cpp b/src/javascript/jsc/bindings/BunJSCModule.cpp index c9c2e6cfa..027cb3778 100644 --- a/src/javascript/jsc/bindings/BunJSCModule.cpp +++ b/src/javascript/jsc/bindings/BunJSCModule.cpp @@ -10,12 +10,66 @@ #include "JavaScriptCore/APICast.h" #include "JavaScriptCore/JSBasePrivate.h" #include "JavaScriptCore/ObjectConstructor.h" - +#include "JavaScriptCore/RemoteInspectorServer.h" +#include "JavaScriptCore/AggregateError.h" +#include "JavaScriptCore/BytecodeIndex.h" +#include "JavaScriptCore/CallFrameInlines.h" +#include "JavaScriptCore/ClassInfo.h" +#include "JavaScriptCore/CodeBlock.h" +#include "JavaScriptCore/CodeCache.h" +#include "JavaScriptCore/Completion.h" +#include "JavaScriptCore/Error.h" +#include "JavaScriptCore/ErrorInstance.h" #include "mimalloc.h" using namespace JSC; using namespace WTF; +JSC_DECLARE_HOST_FUNCTION(functionStartRemoteDebugger); +JSC_DEFINE_HOST_FUNCTION(functionStartRemoteDebugger, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + + static const char* defaultHost = "127.0.0.1\0"; + static uint16_t defaultPort = 9230; // node + 1 + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSC::JSValue hostValue = callFrame->argument(0); + JSC::JSValue portValue = callFrame->argument(1); + const char* host = defaultHost; + if (hostValue.isString()) { + + auto str = hostValue.toWTFString(globalObject); + if (!str.isEmpty()) + host = toCString(str).data(); + } else if (!hostValue.isUndefined()) { + throwVMError(globalObject, scope, createTypeError(globalObject, "host must be a string"_s)); + return JSC::JSValue::encode(JSC::jsUndefined()); + } + + uint16_t port = defaultPort; + if (portValue.isNumber()) { + auto port_int = portValue.toUInt32(globalObject); + if (!(port_int > 0 && port_int < 65536)) { + throwVMError(globalObject, scope, createRangeError(globalObject, "port must be between 0 and 65535"_s)); + return JSC::JSValue::encode(JSC::jsUndefined()); + } + port = port_int; + } else if (!portValue.isUndefined()) { + throwVMError(globalObject, scope, createTypeError(globalObject, "port must be a number between 0 and 65535"_s)); + return JSC::JSValue::encode(JSC::jsUndefined()); + } + + globalObject->setRemoteDebuggingEnabled(true); + auto& server = Inspector::RemoteInspectorServer::singleton(); + if (!server.start(reinterpret_cast<const char*>(host), port)) { + throwVMError(globalObject, scope, createError(globalObject, "Failed to start server \""_s + host + ":"_s + port + "\". Is port already in use?"_s)); + return JSC::JSValue::encode(JSC::jsUndefined()); + } + + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsUndefined())); +} + JSC_DECLARE_HOST_FUNCTION(functionDescribe); JSC_DEFINE_HOST_FUNCTION(functionDescribe, (JSGlobalObject * globalObject, CallFrame* callFrame)) { @@ -266,27 +320,28 @@ JSC::JSObject* createJSCModule(JSC::JSGlobalObject* globalObject) { JSC::ObjectInitializationScope initializationScope(vm); - object = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 20); + object = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 21); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "callerSourceOrigin"_s), 1, functionCallerSourceOrigin, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "describe"_s), 1, functionDescribe, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "describeArray"_s), 1, functionDescribeArray, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "gcAndSweep"_s), 1, functionGCAndSweep, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "fullGC"_s), 1, functionFullGC, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "drainMicrotasks"_s), 1, functionDrainMicrotasks, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "edenGC"_s), 1, functionEdenGC, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "fullGC"_s), 1, functionFullGC, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "gcAndSweep"_s), 1, functionGCAndSweep, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "getRandomSeed"_s), 1, functionGetRandomSeed, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "heapSize"_s), 1, functionHeapSize, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "heapStats"_s), 1, functionMemoryUsageStatistics, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "memoryUsage"_s), 1, functionCreateMemoryFootprint, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "getRandomSeed"_s), 1, functionGetRandomSeed, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "setRandomSeed"_s), 1, functionSetRandomSeed, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "isRope"_s), 1, functionIsRope, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "callerSourceOrigin"_s), 1, functionCallerSourceOrigin, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "memoryUsage"_s), 1, functionCreateMemoryFootprint, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "noFTL"_s), 1, functionNoFTL, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "noOSRExitFuzzing"_s), 1, functionNoOSRExitFuzzing, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "optimizeNextInvocation"_s), 1, functionOptimizeNextInvocation, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "numberOfDFGCompiles"_s), 1, functionNumberOfDFGCompiles, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "optimizeNextInvocation"_s), 1, functionOptimizeNextInvocation, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "releaseWeakRefs"_s), 1, functionReleaseWeakRefs, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "totalCompileTime"_s), 1, functionTotalCompileTime, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "reoptimizationRetryCount"_s), 1, functionReoptimizationRetryCount, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); - object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "drainMicrotasks"_s), 1, functionDrainMicrotasks, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "setRandomSeed"_s), 1, functionSetRandomSeed, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "startRemoteDebugger"_s), 2, functionStartRemoteDebugger, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); + object->putDirectNativeFunction(vm, globalObject, JSC::Identifier::fromString(vm, "totalCompileTime"_s), 1, functionTotalCompileTime, NoIntrinsic, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0); } return object; diff --git a/src/javascript/jsc/bindings/ScriptExecutionContext.cpp b/src/javascript/jsc/bindings/ScriptExecutionContext.cpp new file mode 100644 index 000000000..67dd30f6f --- /dev/null +++ b/src/javascript/jsc/bindings/ScriptExecutionContext.cpp @@ -0,0 +1,60 @@ + +#include "ScriptExecutionContext.h" +#include <uws/uSockets/src/libusockets.h> +#include <uws/src/Loop.h> + +extern "C" void Bun__startLoop(us_loop_t* loop); + +namespace WebCore { + +template<bool isSSL> +us_socket_context_t* webSocketContext() +{ + if constexpr (isSSL) { + if (!m_ssl_client_websockets_ctx) { + us_loop_t* loop = (us_loop_t*)uWs::Loop::get(); + us_socket_context_options_t opts; + memset(&opts, 0, sizeof(us_socket_context_t)); + this->m_ssl_client_websockets_ctx = us_create_socket_context(1, loop, sizeof(*ScriptExecutionContext), opts); + *us_socket_context_ext(m_ssl_client_websockets_ctx) = this; + WebSocketStream::registerHTTPContext(this, m_ssl_client_websockets_ctx, loop); + } + + return m_ssl_client_websockets_ctx; + } else { + if (!m_client_websockets_ctx) { + us_loop_t* loop = (us_loop_t*)uWs::Loop::get(); + us_socket_context_options_t opts; + memset(&opts, 0, sizeof(us_socket_context_t)); + this->m_client_websockets_ctx = us_create_socket_context(0, loop, sizeof(*ScriptExecutionContext), opts); + *us_socket_context_ext(m_client_websockets_ctx) = this; + SecureWebSocketStream::registerHTTPContext(this, m_client_websockets_ctx, loop); + } + + return m_client_websockets_ctx; + } +} + +template<bool isSSL, bool isServer> +uWS::WebSocketContext<isSSL, isServer, ScriptExecutionContext*>* +{ + if constexpr (isSSL) { + if (!m_connected_ssl_client_websockets_ctx) { + // should be the parent + RELEASE_ASSERT(m_ssl_client_websockets_ctx); + m_connected_client_websockets_ctx = SecureWebSocketStream::registerClientContext(this, webSocketContext<isSSL>(), loop); + } + + return m_connected_ssl_client_websockets_ctx; + } else { + if (!m_connected_client_websockets_ctx) { + // should be the parent + RELEASE_ASSERT(m_client_websockets_ctx); + m_connected_client_websockets_ctx = WebSocketStream::registerClientContext(this, webSocketContext<isSSL>(), loop); + } + + return m_connected_client_websockets_ctx; + } +} + +}
\ No newline at end of file diff --git a/src/javascript/jsc/bindings/ScriptExecutionContext.h b/src/javascript/jsc/bindings/ScriptExecutionContext.h index 4265ffccd..8f4e2edfd 100644 --- a/src/javascript/jsc/bindings/ScriptExecutionContext.h +++ b/src/javascript/jsc/bindings/ScriptExecutionContext.h @@ -10,6 +10,10 @@ #include <wtf/text/WTFString.h> #include "CachedScript.h" #include "wtf/URL.h" +#include <uws/src/WebSocketContext.h> +struct us_socket_t; +struct us_socket_context_t; + namespace WebCore { class ScriptExecutionContext : public CanMakeWeakPtr<ScriptExecutionContext> { @@ -58,6 +62,13 @@ public: { return m_globalObject; } + + template<bool isSSL> + us_socket_context_t* webSocketContext(); + + template<bool isSSL, bool isServer> + uWS::WebSocketContext<isSSL, isServer, ScriptExecutionContext*>* connnectedWebSocketContext(); + const WTF::URL& url() const { return m_url; } bool activeDOMObjectsAreSuspended() { return false; } bool activeDOMObjectsAreStopped() { return false; } @@ -72,7 +83,7 @@ public: // { // } - void postTask(Task&&) + void postTask(Task&& task) { } // Executes the task on context's thread asynchronously. @@ -91,5 +102,11 @@ private: JSC::VM* m_vm = nullptr; JSC::JSGlobalObject* m_globalObject = nullptr; WTF::URL m_url = WTF::URL(); + + us_socket_context_t* m_ssl_client_websockets_ctx = nullptr; + us_socket_context_t* m_client_websockets_ctx = nullptr; + + uWS::WebSocketContext<true, false, ScriptExecutionContext*>* m_ssl_client_websockets_ctx = nullptr; + uWS::WebSocketContext<true, true, ScriptExecutionContext*>* m_client_websockets_ctx = nullptr; }; }
\ No newline at end of file diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig index f061e50b5..841fa7a38 100644 --- a/src/javascript/jsc/bindings/bindings.zig +++ b/src/javascript/jsc/bindings/bindings.zig @@ -1620,6 +1620,10 @@ pub const JSGlobalObject = extern struct { return @ptrCast(*JSC.VirtualMachine, @alignCast(std.meta.alignment(JSC.VirtualMachine), this.bunVM_())); } + pub fn startRemoteInspector(this: *JSGlobalObject, host: [:0]const u8, port: u16) bool { + return cppFn("startRemoteInspector", .{ this, host, port }); + } + extern fn ZigGlobalObject__readableStreamToArrayBuffer(*JSGlobalObject, JSValue) JSValue; extern fn ZigGlobalObject__readableStreamToText(*JSGlobalObject, JSValue) JSValue; extern fn ZigGlobalObject__readableStreamToJSON(*JSGlobalObject, JSValue) JSValue; @@ -1677,6 +1681,7 @@ pub const JSGlobalObject = extern struct { "asyncGeneratorFunctionPrototype", "vm", "generateHeapSnapshot", + "startRemoteInspector", // "createError", // "throwError", }; diff --git a/src/javascript/jsc/bindings/webcore/JSWebSocket.cpp b/src/javascript/jsc/bindings/webcore/JSWebSocket.cpp new file mode 100644 index 000000000..aa351fba3 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/JSWebSocket.cpp @@ -0,0 +1,705 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "JSWebSocket.h" + +#include "ActiveDOMObject.h" +#include "EventNames.h" +#include "ExtendedDOMClientIsoSubspaces.h" +#include "ExtendedDOMIsoSubspaces.h" +#include "IDLTypes.h" +// #include "JSBlob.h" +#include "JSDOMAttribute.h" +#include "JSDOMBinding.h" +#include "JSDOMConstructor.h" +#include "JSDOMConvertBase.h" +#include "JSDOMConvertBufferSource.h" +#include "JSDOMConvertInterface.h" +#include "JSDOMConvertNullable.h" +#include "JSDOMConvertNumbers.h" +#include "JSDOMConvertSequences.h" +#include "JSDOMConvertStrings.h" +#include "JSDOMExceptionHandling.h" +#include "JSDOMGlobalObjectInlines.h" +#include "JSDOMOperation.h" +#include "JSDOMWrapperCache.h" +#include "JSEventListener.h" +#include "ScriptExecutionContext.h" +#include "WebCoreJSClientData.h" +#include <JavaScriptCore/HeapAnalyzer.h> +#include <JavaScriptCore/IteratorOperations.h> +#include <JavaScriptCore/JSArray.h> +#include <JavaScriptCore/JSCInlines.h> +#include <JavaScriptCore/JSDestructibleObjectHeapCellType.h> +#include <JavaScriptCore/SlotVisitorMacros.h> +#include <JavaScriptCore/SubspaceInlines.h> +#include <wtf/GetPtr.h> +#include <wtf/PointerPreparations.h> +#include <wtf/URL.h> + +namespace WebCore { +using namespace JSC; + +// Functions + +static JSC_DECLARE_HOST_FUNCTION(jsWebSocketPrototypeFunction_send); +static JSC_DECLARE_HOST_FUNCTION(jsWebSocketPrototypeFunction_close); + +// Attributes + +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocketConstructor); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_URL); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_url); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_readyState); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_bufferedAmount); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_onopen); +static JSC_DECLARE_CUSTOM_SETTER(setJSWebSocket_onopen); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_onmessage); +static JSC_DECLARE_CUSTOM_SETTER(setJSWebSocket_onmessage); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_onerror); +static JSC_DECLARE_CUSTOM_SETTER(setJSWebSocket_onerror); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_onclose); +static JSC_DECLARE_CUSTOM_SETTER(setJSWebSocket_onclose); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_protocol); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_extensions); +static JSC_DECLARE_CUSTOM_GETTER(jsWebSocket_binaryType); +static JSC_DECLARE_CUSTOM_SETTER(setJSWebSocket_binaryType); + +class JSWebSocketPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static JSWebSocketPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure) + { + JSWebSocketPrototype* ptr = new (NotNull, JSC::allocateCell<JSWebSocketPrototype>(vm)) JSWebSocketPrototype(vm, globalObject, structure); + ptr->finishCreation(vm); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWebSocketPrototype, Base); + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSWebSocketPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure) + : JSC::JSNonFinalObject(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWebSocketPrototype, JSWebSocketPrototype::Base); + +using JSWebSocketDOMConstructor = JSDOMConstructor<JSWebSocket>; + +/* Hash table for constructor */ + +static const HashTableValue JSWebSocketConstructorTableValues[] = { + { "CONNECTING"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(0) } }, + { "OPEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(1) } }, + { "CLOSING"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(2) } }, + { "CLOSED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(3) } }, +}; + +static_assert(WebSocket::CONNECTING == 0, "CONNECTING in WebSocket does not match value from IDL"); +static_assert(WebSocket::OPEN == 1, "OPEN in WebSocket does not match value from IDL"); +static_assert(WebSocket::CLOSING == 2, "CLOSING in WebSocket does not match value from IDL"); +static_assert(WebSocket::CLOSED == 3, "CLOSED in WebSocket does not match value from IDL"); + +static inline EncodedJSValue constructJSWebSocket1(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* castedThis = jsCast<JSWebSocketDOMConstructor*>(callFrame->jsCallee()); + ASSERT(castedThis); + auto* context = castedThis->scriptExecutionContext(); + if (UNLIKELY(!context)) + return throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, "WebSocket"); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto url = convert<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->argument(1); + auto protocols = argument1.value().isUndefined() ? Converter<IDLSequence<IDLDOMString>>::ReturnType {} : convert<IDLSequence<IDLDOMString>>(*lexicalGlobalObject, argument1.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + auto object = WebSocket::create(*context, WTFMove(url), WTFMove(protocols)); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef); + auto jsValue = toJSNewlyCreated<IDLInterface<WebSocket>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTFMove(object)); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + setSubclassStructureIfNeeded<WebSocket>(lexicalGlobalObject, callFrame, asObject(jsValue)); + RETURN_IF_EXCEPTION(throwScope, {}); + return JSValue::encode(jsValue); +} + +static inline EncodedJSValue constructJSWebSocket2(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* castedThis = jsCast<JSWebSocketDOMConstructor*>(callFrame->jsCallee()); + ASSERT(castedThis); + auto* context = castedThis->scriptExecutionContext(); + if (UNLIKELY(!context)) + return throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, "WebSocket"); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto url = convert<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); + auto protocol = convert<IDLDOMString>(*lexicalGlobalObject, argument1.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + auto object = WebSocket::create(*context, WTFMove(url), WTFMove(protocol)); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef); + auto jsValue = toJSNewlyCreated<IDLInterface<WebSocket>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTFMove(object)); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + setSubclassStructureIfNeeded<WebSocket>(lexicalGlobalObject, callFrame, asObject(jsValue)); + RETURN_IF_EXCEPTION(throwScope, {}); + return JSValue::encode(jsValue); +} + +template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWebSocketDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + size_t argsCount = std::min<size_t>(2, callFrame->argumentCount()); + if (argsCount == 1) { + RELEASE_AND_RETURN(throwScope, (constructJSWebSocket1(lexicalGlobalObject, callFrame))); + } + if (argsCount == 2) { + JSValue distinguishingArg = callFrame->uncheckedArgument(1); + if (distinguishingArg.isUndefined()) + RELEASE_AND_RETURN(throwScope, (constructJSWebSocket1(lexicalGlobalObject, callFrame))); + { + bool success = hasIteratorMethod(lexicalGlobalObject, distinguishingArg); + RETURN_IF_EXCEPTION(throwScope, {}); + if (success) + RELEASE_AND_RETURN(throwScope, (constructJSWebSocket1(lexicalGlobalObject, callFrame))); + } + RELEASE_AND_RETURN(throwScope, (constructJSWebSocket2(lexicalGlobalObject, callFrame))); + } + return argsCount < 1 ? throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)) : throwVMTypeError(lexicalGlobalObject, throwScope); +} +JSC_ANNOTATE_HOST_FUNCTION(JSWebSocketConstructorConstruct, JSWebSocketDOMConstructor::construct); + +template<> const ClassInfo JSWebSocketDOMConstructor::s_info = { "WebSocket"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWebSocketDOMConstructor) }; + +template<> JSValue JSWebSocketDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject) +{ + return JSEventTarget::getConstructor(vm, &globalObject); +} + +template<> void JSWebSocketDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject) +{ + putDirect(vm, vm.propertyNames->length, jsNumber(1), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + JSString* nameString = jsNontrivialString(vm, "WebSocket"_s); + m_originalName.set(vm, this, nameString); + putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + putDirect(vm, vm.propertyNames->prototype, JSWebSocket::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete); + reifyStaticProperties(vm, JSWebSocket::info(), JSWebSocketConstructorTableValues, *this); +} + +/* Hash table for prototype */ + +static const HashTableValue JSWebSocketPrototypeTableValues[] = { + { "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocketConstructor), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, + { "URL"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_URL), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, + { "url"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_url), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, + { "readyState"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_readyState), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, + { "bufferedAmount"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_bufferedAmount), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, + { "onopen"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_onopen), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_onopen) } }, + { "onmessage"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_onmessage), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_onmessage) } }, + { "onerror"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_onerror), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_onerror) } }, + { "onclose"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_onclose), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_onclose) } }, + { "protocol"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_protocol), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, + { "extensions"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_extensions), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, + { "binaryType"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_binaryType), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_binaryType) } }, + { "send"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsWebSocketPrototypeFunction_send), (intptr_t)(1) } }, + { "close"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsWebSocketPrototypeFunction_close), (intptr_t)(0) } }, + { "CONNECTING"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(0) } }, + { "OPEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(1) } }, + { "CLOSING"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(2) } }, + { "CLOSED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(3) } }, +}; + +const ClassInfo JSWebSocketPrototype::s_info = { "WebSocket"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWebSocketPrototype) }; + +void JSWebSocketPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSWebSocket::info(), JSWebSocketPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +const ClassInfo JSWebSocket::s_info = { "WebSocket"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWebSocket) }; + +JSWebSocket::JSWebSocket(Structure* structure, JSDOMGlobalObject& globalObject, Ref<WebSocket>&& impl) + : JSEventTarget(structure, globalObject, WTFMove(impl)) +{ +} + +void JSWebSocket::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + // static_assert(std::is_base_of<ActiveDOMObject, WebSocket>::value, "Interface is marked as [ActiveDOMObject] but implementation class does not subclass ActiveDOMObject."); +} + +JSObject* JSWebSocket::createPrototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + return JSWebSocketPrototype::create(vm, &globalObject, JSWebSocketPrototype::createStructure(vm, &globalObject, JSEventTarget::prototype(vm, globalObject))); +} + +JSObject* JSWebSocket::prototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + return getDOMPrototype<JSWebSocket>(vm, globalObject); +} + +JSValue JSWebSocket::getConstructor(VM& vm, const JSGlobalObject* globalObject) +{ + return getDOMConstructor<JSWebSocketDOMConstructor, DOMConstructorID::WebSocket>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocketConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* prototype = jsDynamicCast<JSWebSocketPrototype*>(JSValue::decode(thisValue)); + if (UNLIKELY(!prototype)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + return JSValue::encode(JSWebSocket::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject())); +} + +static inline JSValue jsWebSocket_URLGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.url()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_URL, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_URLGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline JSValue jsWebSocket_urlGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLUSVString>(lexicalGlobalObject, throwScope, impl.url()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_url, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_urlGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline JSValue jsWebSocket_readyStateGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLUnsignedShort>(lexicalGlobalObject, throwScope, impl.readyState()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_readyState, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_readyStateGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline JSValue jsWebSocket_bufferedAmountGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLUnsignedLong>(lexicalGlobalObject, throwScope, impl.bufferedAmount()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_bufferedAmount, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_bufferedAmountGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline JSValue jsWebSocket_onopenGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + UNUSED_PARAM(lexicalGlobalObject); + return eventHandlerAttribute(thisObject.wrapped(), eventNames().openEvent, worldForDOMObject(thisObject)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_onopen, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_onopenGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline bool setJSWebSocket_onopenSetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject, JSValue value) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().openEvent, value, thisObject); + vm.writeBarrier(&thisObject, value); + ensureStillAliveHere(value); + + return true; +} + +JSC_DEFINE_CUSTOM_SETTER(setJSWebSocket_onopen, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::set<setJSWebSocket_onopenSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName); +} + +static inline JSValue jsWebSocket_onmessageGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + UNUSED_PARAM(lexicalGlobalObject); + return eventHandlerAttribute(thisObject.wrapped(), eventNames().messageEvent, worldForDOMObject(thisObject)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_onmessage, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_onmessageGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline bool setJSWebSocket_onmessageSetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject, JSValue value) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().messageEvent, value, thisObject); + vm.writeBarrier(&thisObject, value); + ensureStillAliveHere(value); + + return true; +} + +JSC_DEFINE_CUSTOM_SETTER(setJSWebSocket_onmessage, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::set<setJSWebSocket_onmessageSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName); +} + +static inline JSValue jsWebSocket_onerrorGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + UNUSED_PARAM(lexicalGlobalObject); + return eventHandlerAttribute(thisObject.wrapped(), eventNames().errorEvent, worldForDOMObject(thisObject)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_onerror, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_onerrorGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline bool setJSWebSocket_onerrorSetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject, JSValue value) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().errorEvent, value, thisObject); + vm.writeBarrier(&thisObject, value); + ensureStillAliveHere(value); + + return true; +} + +JSC_DEFINE_CUSTOM_SETTER(setJSWebSocket_onerror, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::set<setJSWebSocket_onerrorSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName); +} + +static inline JSValue jsWebSocket_oncloseGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + UNUSED_PARAM(lexicalGlobalObject); + return eventHandlerAttribute(thisObject.wrapped(), eventNames().closeEvent, worldForDOMObject(thisObject)); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_onclose, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_oncloseGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline bool setJSWebSocket_oncloseSetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject, JSValue value) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + setEventHandlerAttribute<JSEventListener>(thisObject.wrapped(), eventNames().closeEvent, value, thisObject); + vm.writeBarrier(&thisObject, value); + ensureStillAliveHere(value); + + return true; +} + +JSC_DEFINE_CUSTOM_SETTER(setJSWebSocket_onclose, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::set<setJSWebSocket_oncloseSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName); +} + +static inline JSValue jsWebSocket_protocolGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLNullable<IDLDOMString>>(lexicalGlobalObject, throwScope, impl.protocol()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_protocol, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_protocolGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline JSValue jsWebSocket_extensionsGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLNullable<IDLDOMString>>(lexicalGlobalObject, throwScope, impl.extensions()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_extensions, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_extensionsGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline JSValue jsWebSocket_binaryTypeGetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + RELEASE_AND_RETURN(throwScope, (toJS<IDLDOMString>(lexicalGlobalObject, throwScope, impl.binaryType()))); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWebSocket_binaryType, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::get<jsWebSocket_binaryTypeGetter, CastedThisErrorBehavior::Assert>(*lexicalGlobalObject, thisValue, attributeName); +} + +static inline bool setJSWebSocket_binaryTypeSetter(JSGlobalObject& lexicalGlobalObject, JSWebSocket& thisObject, JSValue value) +{ + auto& vm = JSC::getVM(&lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto& impl = thisObject.wrapped(); + auto nativeValue = convert<IDLDOMString>(lexicalGlobalObject, value); + RETURN_IF_EXCEPTION(throwScope, false); + invokeFunctorPropagatingExceptionIfNecessary(lexicalGlobalObject, throwScope, [&] { + return impl.setBinaryType(WTFMove(nativeValue)); + }); + return true; +} + +JSC_DEFINE_CUSTOM_SETTER(setJSWebSocket_binaryType, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) +{ + return IDLAttribute<JSWebSocket>::set<setJSWebSocket_binaryTypeSetter>(*lexicalGlobalObject, thisValue, encodedValue, attributeName); +} + +static inline JSC::EncodedJSValue jsWebSocketPrototypeFunction_send1Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWebSocket>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto data = convert<IDLArrayBuffer>(*lexicalGlobalObject, argument0.value(), [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentTypeError(lexicalGlobalObject, scope, 0, "data", "WebSocket", "send", "ArrayBuffer"); }); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.send(*data); }))); +} + +static inline JSC::EncodedJSValue jsWebSocketPrototypeFunction_send2Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWebSocket>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto data = convert<IDLArrayBufferView>(*lexicalGlobalObject, argument0.value(), [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentTypeError(lexicalGlobalObject, scope, 0, "data", "WebSocket", "send", "ArrayBufferView"); }); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.send(data.releaseNonNull()); }))); +} + +// static inline JSC::EncodedJSValue jsWebSocketPrototypeFunction_send3Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWebSocket>::ClassParameter castedThis) +// { +// auto& vm = JSC::getVM(lexicalGlobalObject); +// auto throwScope = DECLARE_THROW_SCOPE(vm); +// UNUSED_PARAM(throwScope); +// UNUSED_PARAM(callFrame); +// auto& impl = castedThis->wrapped(); +// EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); +// auto data = convert<IDLInterface<Blob>>(*lexicalGlobalObject, argument0.value(), [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentTypeError(lexicalGlobalObject, scope, 0, "data", "WebSocket", "send", "Blob"); }); +// RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); +// RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.send(*data); }))); +// } + +static inline JSC::EncodedJSValue jsWebSocketPrototypeFunction_send4Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWebSocket>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto data = convert<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.send(WTFMove(data)); }))); +} + +static inline JSC::EncodedJSValue jsWebSocketPrototypeFunction_sendOverloadDispatcher(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWebSocket>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + size_t argsCount = std::min<size_t>(1, callFrame->argumentCount()); + if (argsCount == 1) { + JSValue distinguishingArg = callFrame->uncheckedArgument(0); + if (distinguishingArg.isObject() && asObject(distinguishingArg)->inherits<JSArrayBuffer>()) + RELEASE_AND_RETURN(throwScope, (jsWebSocketPrototypeFunction_send1Body(lexicalGlobalObject, callFrame, castedThis))); + if (distinguishingArg.isObject() && asObject(distinguishingArg)->inherits<JSArrayBufferView>()) + RELEASE_AND_RETURN(throwScope, (jsWebSocketPrototypeFunction_send2Body(lexicalGlobalObject, callFrame, castedThis))); + // if (distinguishingArg.isObject() && asObject(distinguishingArg)->inherits<JSBlob>()) + // RELEASE_AND_RETURN(throwScope, (jsWebSocketPrototypeFunction_send3Body(lexicalGlobalObject, callFrame, castedThis))); + RELEASE_AND_RETURN(throwScope, (jsWebSocketPrototypeFunction_send4Body(lexicalGlobalObject, callFrame, castedThis))); + } + return argsCount < 1 ? throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)) : throwVMTypeError(lexicalGlobalObject, throwScope); +} + +JSC_DEFINE_HOST_FUNCTION(jsWebSocketPrototypeFunction_send, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSWebSocket>::call<jsWebSocketPrototypeFunction_sendOverloadDispatcher>(*lexicalGlobalObject, *callFrame, "send"); +} + +static inline JSC::EncodedJSValue jsWebSocketPrototypeFunction_closeBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSWebSocket>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->argument(0); + auto code = argument0.value().isUndefined() ? std::optional<Converter<IDLClampAdaptor<IDLUnsignedShort>>::ReturnType>() : std::optional<Converter<IDLClampAdaptor<IDLUnsignedShort>>::ReturnType>(convert<IDLClampAdaptor<IDLUnsignedShort>>(*lexicalGlobalObject, argument0.value())); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->argument(1); + auto reason = argument1.value().isUndefined() ? String() : convert<IDLUSVString>(*lexicalGlobalObject, argument1.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.close(WTFMove(code), WTFMove(reason)); }))); +} + +JSC_DEFINE_HOST_FUNCTION(jsWebSocketPrototypeFunction_close, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSWebSocket>::call<jsWebSocketPrototypeFunction_closeBody>(*lexicalGlobalObject, *callFrame, "close"); +} + +JSC::GCClient::IsoSubspace* JSWebSocket::subspaceForImpl(JSC::VM& vm) +{ + return WebCore::subspaceForImpl<JSWebSocket, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForWebSocket.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForWebSocket = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForWebSocket.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForWebSocket = WTFMove(space); }); +} + +void JSWebSocket::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSWebSocket*>(cell); + analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); + if (thisObject->scriptExecutionContext()) + analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + Base::analyzeHeap(cell, analyzer); +} + +bool JSWebSocketOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, const char** reason) +{ + auto* jsWebSocket = jsCast<JSWebSocket*>(handle.slot()->asCell()); + auto& wrapped = jsWebSocket->wrapped(); + // if (!wrapped.isContextStopped() && wrapped.hasPendingActivity()) { + // if (UNLIKELY(reason)) + // *reason = "ActiveDOMObject with pending activity"; + // return true; + // } + if (jsWebSocket->wrapped().isFiringEventListeners()) { + if (UNLIKELY(reason)) + *reason = "EventTarget firing event listeners"; + return true; + } + UNUSED_PARAM(visitor); + UNUSED_PARAM(reason); + return false; +} + +void JSWebSocketOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context) +{ + auto* jsWebSocket = static_cast<JSWebSocket*>(handle.slot()->asCell()); + auto& world = *static_cast<DOMWrapperWorld*>(context); + uncacheWrapper(world, &jsWebSocket->wrapped(), jsWebSocket); +} + +#if ENABLE(BINDING_INTEGRITY) +#if PLATFORM(WIN) +#pragma warning(disable : 4483) +extern "C" { +extern void (*const __identifier("??_7WebSocket@WebCore@@6B@")[])(); +} +#else +extern "C" { +extern void* _ZTVN7WebCore9WebSocketE[]; +} +#endif +#endif + +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<WebSocket>&& impl) +{ + + if constexpr (std::is_polymorphic_v<WebSocket>) { +#if ENABLE(BINDING_INTEGRITY) + const void* actualVTablePointer = getVTablePointer(impl.ptr()); +#if PLATFORM(WIN) + void* expectedVTablePointer = __identifier("??_7WebSocket@WebCore@@6B@"); +#else + void* expectedVTablePointer = &_ZTVN7WebCore9WebSocketE[2]; +#endif + + // If you hit this assertion you either have a use after free bug, or + // WebSocket has subclasses. If WebSocket has subclasses that get passed + // to toJS() we currently require WebSocket you to opt out of binding hardening + // by adding the SkipVTableValidation attribute to the interface IDL definition + RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer); +#endif + } + return createWrapper<WebSocket>(globalObject, WTFMove(impl)); +} + +JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, WebSocket& impl) +{ + return wrap(lexicalGlobalObject, globalObject, impl); +} + +WebSocket* JSWebSocket::toWrapped(JSC::VM&, JSC::JSValue value) +{ + if (auto* wrapper = jsDynamicCast<JSWebSocket*>(value)) + return &wrapper->wrapped(); + return nullptr; +} + +} diff --git a/src/javascript/jsc/bindings/webcore/JSWebSocket.dep b/src/javascript/jsc/bindings/webcore/JSWebSocket.dep new file mode 100644 index 000000000..69bc1775b --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/JSWebSocket.dep @@ -0,0 +1,2 @@ +JSWebSocket.h : EventTarget.idl +EventTarget.idl : diff --git a/src/javascript/jsc/bindings/webcore/JSWebSocket.h b/src/javascript/jsc/bindings/webcore/JSWebSocket.h new file mode 100644 index 000000000..ae99a9887 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/JSWebSocket.h @@ -0,0 +1,98 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "JSDOMWrapper.h" +#include "JSEventTarget.h" +#include "WebSocket.h" +#include <wtf/NeverDestroyed.h> + +namespace WebCore { + +class JSWebSocket : public JSEventTarget { +public: + using Base = JSEventTarget; + using DOMWrapped = WebSocket; + static JSWebSocket* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<WebSocket>&& impl) + { + JSWebSocket* ptr = new (NotNull, JSC::allocateCell<JSWebSocket>(globalObject->vm())) JSWebSocket(structure, *globalObject, WTFMove(impl)); + ptr->finishCreation(globalObject->vm()); + return ptr; + } + + static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&); + static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&); + static WebSocket* toWrapped(JSC::VM&, JSC::JSValue); + + DECLARE_INFO; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), JSC::NonArray); + } + + static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*); + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + WebSocket& wrapped() const + { + return static_cast<WebSocket&>(Base::wrapped()); + } +protected: + JSWebSocket(JSC::Structure*, JSDOMGlobalObject&, Ref<WebSocket>&&); + + void finishCreation(JSC::VM&); +}; + +class JSWebSocketOwner final : public JSC::WeakHandleOwner { +public: + bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, const char**) final; + void finalize(JSC::Handle<JSC::Unknown>, void* context) final; +}; + +inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, WebSocket*) +{ + static NeverDestroyed<JSWebSocketOwner> owner; + return &owner.get(); +} + +inline void* wrapperKey(WebSocket* wrappableObject) +{ + return wrappableObject; +} + +JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, WebSocket&); +inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, WebSocket* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); } +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<WebSocket>&&); +inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<WebSocket>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); } + +template<> struct JSDOMWrapperConverterTraits<WebSocket> { + using WrapperClass = JSWebSocket; + using ToWrappedReturnType = WebSocket*; +}; + +} // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/WebSocket.cpp b/src/javascript/jsc/bindings/webcore/WebSocket.cpp new file mode 100644 index 000000000..6d03a52b7 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/WebSocket.cpp @@ -0,0 +1,894 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2015-2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "WebSocket.h" + +// #include "Blob.h" +#include "CloseEvent.h" +// #include "ContentSecurityPolicy.h" +// #include "DOMWindow.h" +// #include "Document.h" +#include "Event.h" +#include "EventListener.h" +#include "EventNames.h" +// #include "Frame.h" +// #include "FrameLoader.h" +// #include "FrameLoaderClient.h" +// #include "InspectorInstrumentation.h" +// #include "Logging.h" +#include "MessageEvent.h" +// #include "MixedContentChecker.h" +// #include "ResourceLoadObserver.h" +// #include "ScriptController.h" +#include "ScriptExecutionContext.h" +// #include "SecurityOrigin.h" +// #include "SocketProvider.h" +// #include "ThreadableWebSocketChannel.h" +// #include "WebSocketChannel.h" +// #include "WorkerGlobalScope.h" +// #include "WorkerLoaderProxy.h" +// #include "WorkerThread.h" +#include <JavaScriptCore/ArrayBuffer.h> +#include <JavaScriptCore/ArrayBufferView.h> +#include <JavaScriptCore/ScriptCallStack.h> +#include <wtf/HashSet.h> +#include <wtf/HexNumber.h> +// #include <wtf/IsoMallocInlines.h> +#include <wtf/NeverDestroyed.h> +// #include <wtf/RunLoop.h> +#include <wtf/StdLibExtras.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> + +// #if USE(WEB_THREAD) +// #include "WebCoreThreadRun.h" +// #endif +namespace WebCore { +using WebSocketChannel = WebSocketStream; +using ThreadableWebSocketChannel = WebSocketStream; +using WebSocketChannelClient = WebSocketStream; +WTF_MAKE_ISO_ALLOCATED_IMPL(WebSocket); + +Lock WebSocket::s_allActiveWebSocketsLock; + +const size_t maxReasonSizeInBytes = 123; + +static inline bool isValidProtocolCharacter(UChar character) +{ + // Hybi-10 says "(Subprotocol string must consist of) characters in the range U+0021 to U+007E not including + // separator characters as defined in [RFC2616]." + const UChar minimumProtocolCharacter = '!'; // U+0021. + const UChar maximumProtocolCharacter = '~'; // U+007E. + return character >= minimumProtocolCharacter && character <= maximumProtocolCharacter + && character != '"' && character != '(' && character != ')' && character != ',' && character != '/' + && !(character >= ':' && character <= '@') // U+003A - U+0040 (':', ';', '<', '=', '>', '?', '@'). + && !(character >= '[' && character <= ']') // U+005B - U+005D ('[', '\\', ']'). + && character != '{' && character != '}'; +} + +static bool isValidProtocolString(StringView protocol) +{ + if (protocol.isEmpty()) + return false; + for (auto codeUnit : protocol.codeUnits()) { + if (!isValidProtocolCharacter(codeUnit)) + return false; + } + return true; +} + +static String encodeProtocolString(const String& protocol) +{ + StringBuilder builder; + for (size_t i = 0; i < protocol.length(); i++) { + if (protocol[i] < 0x20 || protocol[i] > 0x7E) + builder.append("\\u", hex(protocol[i], 4)); + else if (protocol[i] == 0x5c) + builder.append("\\\\"); + else + builder.append(protocol[i]); + } + return builder.toString(); +} + +static String joinStrings(const Vector<String>& strings, const char* separator) +{ + StringBuilder builder; + for (size_t i = 0; i < strings.size(); ++i) { + if (i) + builder.append(separator); + builder.append(strings[i]); + } + return builder.toString(); +} + +static unsigned saturateAdd(unsigned a, unsigned b) +{ + if (std::numeric_limits<unsigned>::max() - a < b) + return std::numeric_limits<unsigned>::max(); + return a + b; +} + +ASCIILiteral WebSocket::subprotocolSeparator() +{ + return ", "_s; +} + +WebSocket::WebSocket(ScriptExecutionContext& context) + : ContextDestructionObserver(&context) + , m_subprotocol(emptyString()) + , m_extensions(emptyString()) + , m_handshake(url, ) + +{ + Locker locker { allActiveWebSocketsLock() }; + allActiveWebSockets().add(this); +} + +WebSocket::~WebSocket() +{ + { + Locker locker { allActiveWebSocketsLock() }; + allActiveWebSockets().remove(this); + } + + if (m_channel) + m_channel->disconnect(); +} + +ExceptionOr<Ref<WebSocket>> WebSocket::create(ScriptExecutionContext& context, const String& url) +{ + return create(context, url, Vector<String> {}); +} + +ExceptionOr<Ref<WebSocket>> WebSocket::create(ScriptExecutionContext& context, const String& url, const Vector<String>& protocols) +{ + if (url.isNull()) + return Exception { SyntaxError }; + + auto socket = adoptRef(*new WebSocket(context, url)); + // socket->suspendIfNeeded(); + + auto result = socket->connect(url.string(), protocols); + // auto result = socket->connect(url, protocols); + + if (result.hasException()) + return result.releaseException(); + + return socket; +} + +ExceptionOr<Ref<WebSocket>> WebSocket::create(ScriptExecutionContext& context, const String& url, const String& protocol) +{ + return create(context, url, Vector<String> { 1, protocol }); +} + +HashSet<WebSocket*>& WebSocket::allActiveWebSockets() +{ + static NeverDestroyed<HashSet<WebSocket*>> activeWebSockets; + return activeWebSockets; +} + +Lock& WebSocket::allActiveWebSocketsLock() +{ + return s_allActiveWebSocketsLock; +} + +ExceptionOr<void> WebSocket::connect(const String& url) +{ + return connect(url, Vector<String> {}); +} + +ExceptionOr<void> WebSocket::connect(const String& url, const String& protocol) +{ + return connect(url, Vector<String> { 1, protocol }); +} + +void WebSocket::failAsynchronously() +{ + // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this] { + // We must block this connection. Instead of throwing an exception, we indicate this + // using the error event. But since this code executes as part of the WebSocket's + // constructor, we have to wait until the constructor has completed before firing the + // event; otherwise, users can't connect to the event. + this->dispatchErrorEventIfNeeded(); + // this->(); + // this->stop(); + // }); +} + +static String resourceName(const URL& url) +{ + auto path = url.path(); + auto result = makeString( + path, + path.isEmpty() ? "/" : "", + url.queryWithLeadingQuestionMark()); + ASSERT(!result.isEmpty()); + ASSERT(!result.contains(' ')); + return result; +} + +static String hostName(const URL& url, bool secure) +{ + ASSERT(url.protocolIs("wss") == secure); + if (url.port() && ((!secure && url.port().value() != 80) || (secure && url.port().value() != 443))) + return makeString(asASCIILowercase(url.host()), ':', url.port().value()); + return url.host().convertToASCIILowercase(); +} + +ExceptionOr<void> WebSocket::connect(const String& url, const Vector<String>& protocols) +{ + LOG(Network, "WebSocket %p connect() url='%s'", this, url.utf8().data()); + m_url = URL { url }; + + ASSERT(scriptExecutionContext()); + auto& context = *scriptExecutionContext(); + + if (!m_url.isValid()) { + // context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, ); + m_state = CLOSED; + return Exception { SyntaxError, "Invalid url for WebSocket " + m_url.stringCenterEllipsizedToLength() }; + } + + bool is_secure = m_url.protocolIs("wss"); + + if (!m_url.protocolIs("ws") && !is_secure) { + // context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, ); + m_state = CLOSED; + return Exception { SyntaxError, "Wrong url scheme for WebSocket " + m_url.stringCenterEllipsizedToLength() }; + } + if (m_url.hasFragmentIdentifier()) { + // context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, ); + m_state = CLOSED; + return Exception { SyntaxError, "URL has fragment component " + m_url.stringCenterEllipsizedToLength() }; + } + + // ASSERT(context.contentSecurityPolicy()); + // auto& contentSecurityPolicy = *context.contentSecurityPolicy(); + + // contentSecurityPolicy.upgradeInsecureRequestIfNeeded(m_url, ContentSecurityPolicy::InsecureRequestType::Load); + + // if (!portAllowed(m_url)) { + // String message; + // if (m_url.port()) + // message = makeString("WebSocket port ", m_url.port().value(), " blocked"); + // else + // message = "WebSocket without port blocked"_s; + // context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, message); + // failAsynchronously(); + // return {}; + // } + + // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. + // if (!context.shouldBypassMainWorldContentSecurityPolicy() && !contentSecurityPolicy.allowConnectToSource(m_url)) { + // m_state = CLOSED; + + // // FIXME: Should this be throwing an exception? + // return Exception { SecurityError }; + // } + + // FIXME: There is a disagreement about restriction of subprotocols between WebSocket API and hybi-10 protocol + // draft. The former simply says "only characters in the range U+0021 to U+007E are allowed," while the latter + // imposes a stricter rule: "the elements MUST be non-empty strings with characters as defined in [RFC2616], + // and MUST all be unique strings." + // + // Here, we throw SyntaxError if the given protocols do not meet the latter criteria. This behavior does not + // comply with WebSocket API specification, but it seems to be the only reasonable way to handle this conflict. + for (auto& protocol : protocols) { + if (!isValidProtocolString(protocol)) { + // context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, ); + m_state = CLOSED; + return Exception { SyntaxError, "Wrong protocol for WebSocket '" + encodeProtocolString(protocol) + "'" }; + } + } + HashSet<String> visited; + for (auto& protocol : protocols) { + if (!visited.add(protocol).isNewEntry) { + // context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, ); + m_state = CLOSED; + return Exception { SyntaxError, "WebSocket protocols contain duplicates: '" + encodeProtocolString(protocol) + "'" }; + } + } + + // RunLoop::main().dispatch([targetURL = m_url.isolatedCopy(), mainFrameURL = context.url().isolatedCopy()]() { + // ResourceLoadObserver::shared().logWebSocketLoading(targetURL, mainFrameURL); + // }); + + // if (is<Document>(context)) { + // Document& document = downcast<Document>(context); + // RefPtr<Frame> frame = document.frame(); + // // FIXME: make the mixed content check equivalent to the non-document mixed content check currently in WorkerThreadableWebSocketChannel::Bridge::connect() + // if (!frame || !MixedContentChecker::canRunInsecureContent(*frame, document.securityOrigin(), m_url)) { + // failAsynchronously(); + // return { }; + // } + // } + + String protocolString; + if (!protocols.isEmpty()) + protocolString = joinStrings(protocols, subprotocolSeparator()); + + ZigString host = Zig::toZigString(m_url.host()); + auto resource = resourceName(m_url); + ZigString path = Zig::toZigString(resource); + ZigString clientProtocolString = Zig::toZigString(protocolString); + uint16_t port = m_url.port(); + + if (is_secure) { + us_socket_context_t* ctx = scriptExecutionContext->webSocketContext<true>(); + RELEASE_ASSERT(ctx); + this->m_upgradeClient = Bun_SecureWebSocketUpgradeClient__connect(scriptExecutionContext->jsGlobalObject(), ctx, this, &host, port, &path, &clientProtocolString); + } else { + us_socket_context_t* ctx = scriptExecutionContext->webSocketContext<false>(); + RELEASE_ASSERT(ctx); + this->m_upgradeClient = Bun_WebSocketUpgradeClient__connect(scriptExecutionContext->jsGlobalObject(), ctx, this, &host, port, &path, &clientProtocolString); + } + + if (this->m_upgradeClient == nullptr) { + // context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, ); + m_state = CLOSED; + return Exception { SyntaxError, "WebSocket connection failed" }; + } + + m_state = CONNECTING; + + // #if ENABLE(INTELLIGENT_TRACKING_PREVENTION) + // auto reportRegistrableDomain = [domain = RegistrableDomain(m_url).isolatedCopy()](auto& context) mutable { + // if (auto* frame = downcast<Document>(context).frame()) + // frame->loader().client().didLoadFromRegistrableDomain(WTFMove(domain)); + // }; + // if (is<Document>(context)) + // reportRegistrableDomain(context); + // else + // downcast<WorkerGlobalScope>(context).thread().workerLoaderProxy().postTaskToLoader(WTFMove(reportRegistrableDomain)); + // #endif + + // m_pendingActivity = makePendingActivity(*this); + + return {}; +} + +ExceptionOr<void> WebSocket::send(const String& message) +{ + LOG(Network, "WebSocket %p send() Sending String '%s'", this, message.utf8().data()); + if (m_state == CONNECTING) + return Exception { InvalidStateError }; + auto utf8 = message.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD); + // No exception is raised if the connection was once established but has subsequently been closed. + if (m_state == CLOSING || m_state == CLOSED) { + size_t payloadSize = utf8.length(); + m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); + m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); + return {}; + } + // FIXME: WebSocketChannel also has a m_bufferedAmount. Remove that one. This one is the correct one accessed by JS. + m_bufferedAmount = saturateAdd(m_bufferedAmount, utf8.length()); + ASSERT(m_channel); + m_channel->send(WTFMove(utf8)); + return {}; +} + +ExceptionOr<void> WebSocket::send(ArrayBuffer& binaryData) +{ + LOG(Network, "WebSocket %p send() Sending ArrayBuffer %p", this, &binaryData); + if (m_state == CONNECTING) + return Exception { InvalidStateError }; + if (m_state == CLOSING || m_state == CLOSED) { + unsigned payloadSize = binaryData.byteLength(); + m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); + m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); + return {}; + } + m_bufferedAmount = saturateAdd(m_bufferedAmount, binaryData.byteLength()); + ASSERT(m_channel); + m_channel->send(binaryData, 0, binaryData.byteLength()); + return {}; +} + +ExceptionOr<void> WebSocket::send(ArrayBufferView& arrayBufferView) +{ + LOG(Network, "WebSocket %p send() Sending ArrayBufferView %p", this, &arrayBufferView); + + if (m_state == CONNECTING) + return Exception { InvalidStateError }; + if (m_state == CLOSING || m_state == CLOSED) { + unsigned payloadSize = arrayBufferView.byteLength(); + m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); + m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); + return {}; + } + m_bufferedAmount = saturateAdd(m_bufferedAmount, arrayBufferView.byteLength()); + ASSERT(m_channel); + m_channel->send(*arrayBufferView.unsharedBuffer(), arrayBufferView.byteOffset(), arrayBufferView.byteLength()); + return {}; +} + +// ExceptionOr<void> WebSocket::send(Blob& binaryData) +// { +// LOG(Network, "WebSocket %p send() Sending Blob '%s'", this, binaryData.url().stringCenterEllipsizedToLength().utf8().data()); +// if (m_state == CONNECTING) +// return Exception { InvalidStateError }; +// if (m_state == CLOSING || m_state == CLOSED) { +// unsigned payloadSize = static_cast<unsigned>(binaryData.size()); +// m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); +// m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); +// return {}; +// } +// m_bufferedAmount = saturateAdd(m_bufferedAmount, binaryData.size()); +// ASSERT(m_channel); +// m_channel->send(binaryData); +// return {}; +// } + +ExceptionOr<void> WebSocket::close(std::optional<unsigned short> optionalCode, const String& reason) +{ + int code = optionalCode ? optionalCode.value() : static_cast<int>(WebSocketChannel::CloseEventCodeNotSpecified); + if (code == WebSocketChannel::CloseEventCodeNotSpecified) + LOG(Network, "WebSocket %p close() without code and reason", this); + else { + LOG(Network, "WebSocket %p close() code=%d reason='%s'", this, code, reason.utf8().data()); + // if (!(code == WebSocketChannel::CloseEventCodeNormalClosure || (WebSocketChannel::CloseEventCodeMinimumUserDefined <= code && code <= WebSocketChannel::CloseEventCodeMaximumUserDefined))) + // return Exception { InvalidAccessError }; + CString utf8 = reason.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD); + if (utf8.length() > maxReasonSizeInBytes) { + // scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "WebSocket close message is too long."_s); + return Exception { SyntaxError, "WebSocket close message is too long."_s }; + } + } + + if (m_state == CLOSING || m_state == CLOSED) + return {}; + if (m_state == CONNECTING) { + m_state = CLOSING; + m_channel->fail("WebSocket is closed before the connection is established."_s); + return {}; + } + m_state = CLOSING; + if (m_channel) + m_channel->close(code, reason); + return {}; +} + +WebSocketChannelClient* WebSocket::channel() const +{ + return m_channel; +} + +const URL& WebSocket::url() const +{ + return m_url; +} + +WebSocket::State WebSocket::readyState() const +{ + return m_state; +} + +unsigned WebSocket::bufferedAmount() const +{ + return saturateAdd(m_bufferedAmount, m_bufferedAmountAfterClose); +} + +String WebSocket::protocol() const +{ + return m_subprotocol; +} + +String WebSocket::extensions() const +{ + return m_extensions; +} + +String WebSocket::binaryType() const +{ + switch (m_binaryType) { + // case BinaryType::Blob: + // return "blob"_s; + case BinaryType::ArrayBuffer: + return "arraybuffer"_s; + } + ASSERT_NOT_REACHED(); + return String(); +} + +ExceptionOr<void> WebSocket::setBinaryType(const String& binaryType) +{ + // if (binaryType == "blob") { + // m_binaryType = BinaryType::Blob; + // return {}; + // } + if (binaryType == "arraybuffer") { + m_binaryType = BinaryType::ArrayBuffer; + return {}; + } + // scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "'" + binaryType + "' is not a valid value for binaryType; binaryType remains unchanged."); + return Exception { SyntaxError, "'" + binaryType + "' is not a valid value for binaryType; binaryType remains unchanged." }; +} + +EventTargetInterface WebSocket::eventTargetInterface() const +{ + return WebSocketEventTargetInterfaceType; +} + +ScriptExecutionContext* WebSocket::scriptExecutionContext() const +{ + return ContextDestructionObserver::scriptExecutionContext(); +} + +// void WebSocket::contextDestroyed() +// { +// LOG(Network, "WebSocket %p contextDestroyed()", this); +// ASSERT(!m_channel); +// ASSERT(m_state == CLOSED); +// // ActiveDOMObject::contextDestroyed(); +// } + +// void WebSocket::suspend(ReasonForSuspension reason) +// { +// // if (!m_channel) +// // return; + +// // if (reason == ReasonForSuspension::BackForwardCache) { +// // // This will cause didClose() to be called. +// // m_channel->fail("WebSocket is closed due to suspension."_s); +// // return; +// // } + +// // m_channel->suspend(); +// } + +// void WebSocket::resume() +// { +// // if (m_channel) +// // m_channel->resume(); +// } + +// void WebSocket::stop() +// { +// if (m_channel) +// m_channel->disconnect(); +// m_channel = nullptr; +// m_state = CLOSED; +// // ActiveDOMObject::stop(); +// // m_pendingActivity = nullptr; +// } + +// const char* WebSocket::activeDOMObjectName() const +// { +// return "WebSocket"; +// } + +void WebSocket::didConnect() +{ + LOG(Network, "WebSocket %p didConnect()", this); + // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this] { + if (m_state == CLOSED) + return; + if (m_state != CONNECTING) { + didClose(0, ClosingHandshakeIncomplete, WebSocketChannel::CloseEventCodeAbnormalClosure, emptyString()); + return; + } + ASSERT(scriptExecutionContext()); + m_state = OPEN; + // m_subprotocol = m_channel->subprotocol(); + // m_extensions = m_channel->extensions(); + dispatchEvent(Event::create(eventNames().openEvent, Event::CanBubble::No, Event::IsCancelable::No)); + // }); +} + +void WebSocket::didReceiveMessage(String&& message) +{ + LOG(Network, "WebSocket %p didReceiveMessage() Text message '%s'", this, message.utf8().data()); + // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this, message = WTFMove(message)]() mutable { + if (m_state != OPEN) + return; + + // if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { + // if (auto* inspector = m_channel->channelInspector()) { + // auto utf8Message = message.utf8(); + // inspector->didReceiveWebSocketFrame(WebSocketChannelInspector::createFrame(utf8Message.dataAsUInt8Ptr(), utf8Message.length(), WebSocketFrame::OpCode::OpCodeText)); + // } + // } + ASSERT(scriptExecutionContext()); + dispatchEvent(MessageEvent::create(WTFMove(message), m_url.string())); + // }); +} + +void WebSocket::didReceiveBinaryData(Vector<uint8_t>&& binaryData) +{ + LOG(Network, "WebSocket %p didReceiveBinaryData() %u byte binary message", this, static_cast<unsigned>(binaryData.size())); + // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this, binaryData = WTFMove(binaryData)]() mutable { + if (m_state != OPEN) + return; + + // if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { + // if (auto* inspector = m_channel->channelInspector()) + // inspector->didReceiveWebSocketFrame(WebSocketChannelInspector::createFrame(binaryData.data(), binaryData.size(), WebSocketFrame::OpCode::OpCodeBinary)); + // } + + switch (m_binaryType) { + // case BinaryType::Blob: + // // FIXME: We just received the data from NetworkProcess, and are sending it back. This is inefficient. + // dispatchEvent(MessageEvent::create(Blob::create(scriptExecutionContext(), WTFMove(binaryData), emptyString()), SecurityOrigin::create(m_url)->toString())); + // break; + case BinaryType::ArrayBuffer: + dispatchEvent(MessageEvent::create(ArrayBuffer::create(binaryData.data(), binaryData.size()), m_url.string())); + break; + } + // }); +} + +void WebSocket::didReceiveMessageError(String&& reason) +{ + LOG(Network, "WebSocket %p didReceiveErrorMessage()", this); + // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this, reason = WTFMove(reason)] { + if (m_state == CLOSED) + return; + m_state = CLOSED; + ASSERT(scriptExecutionContext()); + + // if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { + // if (auto* inspector = m_channel->channelInspector()) + // inspector->didReceiveWebSocketFrameError(reason); + // } + + // FIXME: As per https://html.spec.whatwg.org/multipage/web-sockets.html#feedback-from-the-protocol:concept-websocket-closed, we should synchronously fire a close event. + dispatchErrorEventIfNeeded(); + // }); +} + +void WebSocket::didReceiveMessageError(String& reason) +{ + LOG(Network, "WebSocket %p didReceiveErrorMessage()", this); + // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this, reason = WTFMove(reason)] { + if (m_state == CLOSED) + return; + m_state = CLOSED; + ASSERT(scriptExecutionContext()); + + // if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { + // if (auto* inspector = m_channel->channelInspector()) + // inspector->didReceiveWebSocketFrameError(reason); + // } + + // FIXME: As per https://html.spec.whatwg.org/multipage/web-sockets.html#feedback-from-the-protocol:concept-websocket-closed, we should synchronously fire a close event. + dispatchEvent(CloseEvent::create(false, 0, reason)); + // }); +} + +void WebSocket::didUpdateBufferedAmount(unsigned bufferedAmount) +{ + LOG(Network, "WebSocket %p didUpdateBufferedAmount() New bufferedAmount is %u", this, bufferedAmount); + if (m_state == CLOSED) + return; + m_bufferedAmount = bufferedAmount; +} + +void WebSocket::didStartClosingHandshake() +{ + LOG(Network, "WebSocket %p didStartClosingHandshake()", this); + // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this] { + if (m_state == CLOSED) + return; + m_state = CLOSING; + // }); +} + +void WebSocket::didClose(unsigned unhandledBufferedAmount, ClosingHandshakeCompletionStatus closingHandshakeCompletion, unsigned short code, const String& reason) +{ + LOG(Network, "WebSocket %p didClose()", this); + // queueTaskKeepingObjectAlive(*this, TaskSource::WebSocket, [this, unhandledBufferedAmount, closingHandshakeCompletion, code, reason] { + if (!m_channel) + return; + + // if (UNLIKELY(InspectorInstrumentation::hasFrontends())) { + // if (auto* inspector = m_channel->channelInspector()) { + // WebSocketFrame closingFrame(WebSocketFrame::OpCodeClose, true, false, false); + // inspector->didReceiveWebSocketFrame(closingFrame); + // inspector->didCloseWebSocket(); + // } + // } + + bool wasClean = m_state == CLOSING && !unhandledBufferedAmount && closingHandshakeCompletion == ClosingHandshakeComplete && code != WebSocketChannel::CloseEventCodeAbnormalClosure; + m_state = CLOSED; + m_bufferedAmount = unhandledBufferedAmount; + ASSERT(scriptExecutionContext()); + + dispatchEvent(CloseEvent::create(wasClean, code, reason)); + + if (m_channel) { + m_channel->disconnect(); + m_channel = nullptr; + } + // m_pendingActivity = nullptr; + // }); +} + +void WebSocket::didUpgradeURL() +{ + ASSERT(m_url.protocolIs("ws")); + m_url.setProtocol("wss"); +} + +size_t WebSocket::getFramingOverhead(size_t payloadSize) +{ + static const size_t hybiBaseFramingOverhead = 2; // Every frame has at least two-byte header. + static const size_t hybiMaskingKeyLength = 4; // Every frame from client must have masking key. + static const size_t minimumPayloadSizeWithTwoByteExtendedPayloadLength = 126; + static const size_t minimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000; + size_t overhead = hybiBaseFramingOverhead + hybiMaskingKeyLength; + if (payloadSize >= minimumPayloadSizeWithEightByteExtendedPayloadLength) + overhead += 8; + else if (payloadSize >= minimumPayloadSizeWithTwoByteExtendedPayloadLength) + overhead += 2; + return overhead; +} + +void WebSocket::dispatchErrorEventIfNeeded() +{ + if (m_dispatchedErrorEvent) + return; + + m_dispatchedErrorEvent = true; + dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No)); +} + +void WebSocket::didConnect(us_socket_t* socket, char* bufferedData, size_t bufferedDataSize) +{ + m_state = CONNECTED; + this->m_upgradeClient = nullptr; +} +void WebSocket::didFailToConnect(int32_t code) +{ + m_state = CLOSED; + this->m_upgradeClient = nullptr; + + switch (code) { + // cancel + case 0: { + break; + } + // invalid_response + case 1: { + auto message = MAKE_STATIC_STRING_IMPL("Invalid response"); + didReceiveMessageError(message); + break; + } + // expected_101_status_code + case 2: { + auto message = MAKE_STATIC_STRING_IMPL("Expected 101 status code"); + didReceiveMessageError(message); + break; + } + // missing_upgrade_header + case 3: { + auto message = MAKE_STATIC_STRING_IMPL("Missing upgrade header"); + didReceiveMessageError(message); + break; + } + // missing_connection_header + case 4: { + auto message = MAKE_STATIC_STRING_IMPL("Missing connection header"); + didReceiveMessageError(message); + break; + } + // missing_websocket_accept_header + case 5: { + auto message = MAKE_STATIC_STRING_IMPL("Missing websocket accept header"); + didReceiveMessageError(message); + break; + } + // invalid_upgrade_header + case 6: { + auto message = MAKE_STATIC_STRING_IMPL("Invalid upgrade header"); + didReceiveMessageError(message); + break; + } + // invalid_connection_header + case 7: { + auto message = MAKE_STATIC_STRING_IMPL("Invalid connection header"); + didReceiveMessageError(message); + break; + } + // invalid_websocket_version + case 8: { + auto message = MAKE_STATIC_STRING_IMPL("Invalid websocket version"); + didReceiveMessageError(message); + break; + } + // mismatch_websocket_accept_header + case 9: { + auto message = MAKE_STATIC_STRING_IMPL("Mismatch websocket accept header"); + didReceiveMessageError(message); + break; + } + // missing_client_protocol + case 10: { + auto message = MAKE_STATIC_STRING_IMPL("Missing client protocol"); + didReceiveMessageError(message); + break; + } + // mismatch_client_protocol + case 11: { + auto message = MAKE_STATIC_STRING_IMPL("Mismatch client protocol"); + didReceiveMessageError(message); + break; + } + // timeout + case 12: { + auto message = MAKE_STATIC_STRING_IMPL("Timeout"); + didReceiveMessageError(message); + break; + } + // closed + case 13: { + auto message = MAKE_STATIC_STRING_IMPL("Closed by client"); + didReceiveMessageError(message); + break; + } + // failed_to_write + case 14: { + auto message = MAKE_STATIC_STRING_IMPL("Failed to write"); + didReceiveMessageError(message); + break; + } + // failed_to_connect + case 15: { + auto message = MAKE_STATIC_STRING_IMPL("Failed to connect"); + didReceiveMessageError(message); + break; + } + // headers_too_large + case 16: { + auto message = MAKE_STATIC_STRING_IMPL("Headers too large"); + didReceiveMessageError(message); + break; + } + // ended + case 17: { + auto message = MAKE_STATIC_STRING_IMPL("Closed by server"); + didReceiveMessageError(message); + break; + } + } +} +} // namespace WebCore + +extern "C" WebSocket__didConnect(WebSocket* webSocket, us_socket_t* socket, char* bufferedData, size_t len) +{ + webSocket->didConnect(socket, bufferedData, len); +} +extern "C" WebSocket__didFailToConnect(WebSocket* webSocket, int32_t errorCode) +{ + webSocket->didFailToConnect(socket, errorCode); +}
\ No newline at end of file diff --git a/src/javascript/jsc/bindings/webcore/WebSocket.h b/src/javascript/jsc/bindings/webcore/WebSocket.h new file mode 100644 index 000000000..ed108705c --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/WebSocket.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ContextDestructionObserver.h" +#include "EventTarget.h" +#include "ExceptionOr.h" +#include <wtf/URL.h> +#include <wtf/HashSet.h> +#include <wtf/Lock.h> + +#include "WebSocketStream.h" + +namespace JSC { +class ArrayBuffer; +class ArrayBufferView; +} + +namespace WebCore { + +// class Blob; +class WebSocket final : public RefCounted<WebSocket>, public EventTargetWithInlineData, public ContextDestructionObserver { + WTF_MAKE_ISO_ALLOCATED(WebSocket); + +public: + static ASCIILiteral subprotocolSeparator(); + + static ExceptionOr<Ref<WebSocket>> create(ScriptExecutionContext&, const String& url); + static ExceptionOr<Ref<WebSocket>> create(ScriptExecutionContext&, const String& url, const String& protocol); + static ExceptionOr<Ref<WebSocket>> create(ScriptExecutionContext&, const String& url, const Vector<String>& protocols); + ~WebSocket(); + + static HashSet<WebSocket*>& allActiveWebSockets() WTF_REQUIRES_LOCK(s_allActiveWebSocketsLock); + static Lock& allActiveWebSocketsLock() WTF_RETURNS_LOCK(s_allActiveWebSocketsLock); + + enum State { + CONNECTING = 0, + OPEN = 1, + CLOSING = 2, + CLOSED = 3 + }; + + ExceptionOr<void> connect(const String& url); + ExceptionOr<void> connect(const String& url, const String& protocol); + ExceptionOr<void> connect(const String& url, const Vector<String>& protocols); + + ExceptionOr<void> send(const String& message); + ExceptionOr<void> send(JSC::ArrayBuffer&); + ExceptionOr<void> send(JSC::ArrayBufferView&); + // ExceptionOr<void> send(Blob&); + + ExceptionOr<void> close(std::optional<unsigned short> code, const String& reason); + + WebSocketStream* channel() const; + + const URL& url() const; + State readyState() const; + unsigned bufferedAmount() const; + + String protocol() const; + String extensions() const; + + String binaryType() const; + ExceptionOr<void> setBinaryType(const String&); + + ScriptExecutionContext* scriptExecutionContext() const final; + + using RefCounted::deref; + using RefCounted::ref; + +private: + explicit WebSocket(ScriptExecutionContext&); + + void dispatchErrorEventIfNeeded(); + + // void contextDestroyed() final; + // void suspend(ReasonForSuspension) final; + // void resume() final; + // void stop() final; + // const char* activeDOMObjectName() const final; + + EventTargetInterface eventTargetInterface() const final; + + void refEventTarget() final { ref(); } + void derefEventTarget() final { deref(); } + + void didConnect(); + void didReceiveMessage(String&& message); + void didReceiveBinaryData(Vector<uint8_t>&&); + void didReceiveMessageError(String&& reason); + void didUpdateBufferedAmount(unsigned bufferedAmount); + void didStartClosingHandshake(); + void didClose(unsigned unhandledBufferedAmount, ClosingHandshakeCompletionStatus, unsigned short code, const String& reason); + void didConnect(us_socket_t* socket, char* bufferedData, size_t bufferedDataSize); + void didFailToConnect(int32_t code); + + void failAsynchronously(); + + enum class BinaryType { Blob, + ArrayBuffer }; + + static Lock s_allActiveWebSocketsLock; + WebSocketStream* m_channel { nullptr }; + + State m_state { CONNECTING }; + URL m_url; + unsigned m_bufferedAmount { 0 }; + unsigned m_bufferedAmountAfterClose { 0 }; + BinaryType m_binaryType { BinaryType::ArrayBuffer }; + String m_subprotocol; + String m_extensions; + void* m_upgradeClient { nullptr }; + + bool m_dispatchedErrorEvent { false }; + // RefPtr<PendingActivity<WebSocket>> m_pendingActivity; +}; + +} // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/WebSocket.idl b/src/javascript/jsc/bindings/webcore/WebSocket.idl new file mode 100644 index 000000000..4f65c90f3 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/WebSocket.idl @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2010, 2011 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +[ + ActiveDOMObject, + Exposed=(Window,Worker), + EnabledAtRuntime=WebSocketEnabled +] interface WebSocket : EventTarget { + [CallWith=CurrentScriptExecutionContext] constructor(USVString url, optional sequence<DOMString> protocols = []); + [CallWith=CurrentScriptExecutionContext] constructor(USVString url, DOMString protocol); + + readonly attribute USVString URL; // Lowercased .url is the one in the spec, but leaving .URL for compatibility reasons. + readonly attribute USVString url; + + const unsigned short CONNECTING = 0; + const unsigned short OPEN = 1; + const unsigned short CLOSING = 2; + const unsigned short CLOSED = 3; + readonly attribute unsigned short readyState; + + readonly attribute unsigned long bufferedAmount; + + attribute EventHandler onopen; + attribute EventHandler onmessage; + attribute EventHandler onerror; + attribute EventHandler onclose; + + readonly attribute DOMString? protocol; + readonly attribute DOMString? extensions; + + attribute DOMString binaryType; + + undefined send(ArrayBuffer data); + undefined send(ArrayBufferView data); + undefined send(Blob data); + undefined send(USVString data); + + undefined close(optional [Clamp] unsigned short code, optional USVString reason); +}; diff --git a/src/javascript/jsc/bindings/webcore/WebSocketHandshake.cpp b/src/javascript/jsc/bindings/webcore/WebSocketHandshake.cpp new file mode 100644 index 000000000..90482d668 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/WebSocketHandshake.cpp @@ -0,0 +1,485 @@ +// /* +// * Copyright (C) 2011 Google Inc. All rights reserved. +// * Copyright (C) Research In Motion Limited 2011. All rights reserved. +// * Copyright (C) 2018-2021 Apple Inc. All rights reserved. +// * +// * Redistribution and use in source and binary forms, with or without +// * modification, are permitted provided that the following conditions are +// * met: +// * +// * * Redistributions of source code must retain the above copyright +// * notice, this list of conditions and the following disclaimer. +// * * Redistributions in binary form must reproduce the above +// * copyright notice, this list of conditions and the following disclaimer +// * in the documentation and/or other materials provided with the +// * distribution. +// * * Neither the name of Google Inc. nor the names of its +// * contributors may be used to endorse or promote products derived from +// * this software without specific prior written permission. +// * +// * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// */ + +// #include "config.h" +// #include "WebSocketHandshake.h" + +// #include "HTTPHeaderMap.h" +// #include "HTTPHeaderNames.h" +// #include "HTTPHeaderValues.h" +// #include "HTTPParsers.h" +// #include "ScriptExecutionContext.h" +// #include <wtf/URL.h> +// #include "WebSocket.h" +// #include <wtf/ASCIICType.h> +// #include <wtf/CryptographicallyRandomNumber.h> +// #include <wtf/SHA1.h> +// #include <wtf/StdLibExtras.h> +// #include <wtf/StringExtras.h> +// #include <wtf/Vector.h> +// #include <wtf/text/Base64.h> +// #include <wtf/text/CString.h> +// #include <wtf/text/StringToIntegerConversion.h> +// #include <wtf/text/StringView.h> +// #include <wtf/text/WTFString.h> +// #include <wtf/unicode/CharacterNames.h> + +// namespace WebCore { + +// static String resourceName(const URL& url) +// { +// auto path = url.path(); +// auto result = makeString( +// path, +// path.isEmpty() ? "/" : "", +// url.queryWithLeadingQuestionMark()); +// ASSERT(!result.isEmpty()); +// ASSERT(!result.contains(' ')); +// return result; +// } + +// static String hostName(const URL& url, bool secure) +// { +// ASSERT(url.protocolIs("wss") == secure); +// if (url.port() && ((!secure && url.port().value() != 80) || (secure && url.port().value() != 443))) +// return makeString(asASCIILowercase(url.host()), ':', url.port().value()); +// return url.host().convertToASCIILowercase(); +// } + +// static constexpr size_t maxInputSampleSize = 128; +// static String trimInputSample(const uint8_t* p, size_t length) +// { +// if (length <= maxInputSampleSize) +// return String(p, length); +// return makeString(StringView(p, length).left(maxInputSampleSize), horizontalEllipsis); +// } + +// static String generateSecWebSocketKey() +// { +// static const size_t nonceSize = 16; +// unsigned char key[nonceSize]; +// cryptographicallyRandomValues(key, nonceSize); +// return base64EncodeToString(key, nonceSize); +// } + +// String WebSocketHandshake::getExpectedWebSocketAccept(const String& secWebSocketKey) +// { +// constexpr uint8_t webSocketKeyGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +// SHA1 sha1; +// CString keyData = secWebSocketKey.ascii(); +// sha1.addBytes(keyData.dataAsUInt8Ptr(), keyData.length()); +// sha1.addBytes(webSocketKeyGUID, std::size(webSocketKeyGUID) - 1); +// SHA1::Digest hash; +// sha1.computeHash(hash); +// return base64EncodeToString(hash.data(), SHA1::hashSize); +// } + +// WebSocketHandshake::WebSocketHandshake(const URL& url, const String& protocol, const String& userAgent, const String& clientOrigin, bool allowCookies, bool isAppInitiated) +// : m_url(url) +// , m_clientProtocol(protocol) +// , m_secure(m_url.protocolIs("wss")) +// , m_mode(Incomplete) +// , m_userAgent(userAgent) +// , m_clientOrigin(clientOrigin) +// , m_allowCookies(allowCookies) +// , m_isAppInitiated(isAppInitiated) +// { +// m_secWebSocketKey = generateSecWebSocketKey(); +// m_expectedAccept = getExpectedWebSocketAccept(m_secWebSocketKey); +// } + +// WebSocketHandshake::~WebSocketHandshake() = default; + +// const URL& WebSocketHandshake::url() const +// { +// return m_url; +// } + +// // FIXME: Return type should just be String, not const String. +// const String WebSocketHandshake::host() const +// { +// return m_url.host().convertToASCIILowercase(); +// } + +// const String& WebSocketHandshake::clientProtocol() const +// { +// return m_clientProtocol; +// } + +// void WebSocketHandshake::setClientProtocol(const String& protocol) +// { +// m_clientProtocol = protocol; +// } + +// bool WebSocketHandshake::secure() const +// { +// return m_secure; +// } + +// String WebSocketHandshake::clientLocation() const +// { +// return makeString(m_secure ? "wss" : "ws", "://", hostName(m_url, m_secure), resourceName(m_url)); +// } + +// CString WebSocketHandshake::clientHandshakeMessage() const +// { +// // Keep the following consistent with clientHandshakeRequest just below. + +// // Cookies are not retrieved in the WebContent process. Instead, a proxy object is +// // added in the handshake, and is exchanged for actual cookies in the Network process. + +// // Add no-cache headers to avoid a compatibility issue. There are some proxies that +// // rewrite "Connection: upgrade" to "Connection: close" in the response if a request +// // doesn't contain these headers. + +// auto extensions = m_extensionDispatcher.createHeaderValue(); +// } + +// void WebSocketHandshake::reset() +// { +// m_mode = Incomplete; +// } + +// int WebSocketHandshake::readServerHandshake(const uint8_t* header, size_t len) +// { +// m_mode = Incomplete; +// int statusCode; +// AtomString statusText; +// int lineLength = readStatusLine(header, len, statusCode, statusText); +// if (lineLength == -1) +// return -1; +// if (statusCode == -1) { +// m_mode = Failed; // m_failureReason is set inside readStatusLine(). +// return len; +// } +// // LOG(Network, "WebSocketHandshake %p readServerHandshake() Status code is %d", this, statusCode); + +// m_serverHandshakeResponse = ResourceResponse(); +// m_serverHandshakeResponse.setHTTPStatusCode(statusCode); +// m_serverHandshakeResponse.setHTTPStatusText(statusText); + +// if (statusCode != 101) { +// m_mode = Failed; +// m_failureReason = makeString("Unexpected response code: ", statusCode); +// return len; +// } +// m_mode = Normal; +// if (!memmem(header, len, "\r\n\r\n", 4)) { +// // Just hasn't been received fully yet. +// m_mode = Incomplete; +// return -1; +// } +// auto p = readHTTPHeaders(header + lineLength, header + len); +// if (!p) { +// // LOG(Network, "WebSocketHandshake %p readServerHandshake() readHTTPHeaders() failed", this); +// m_mode = Failed; // m_failureReason is set inside readHTTPHeaders(). +// return len; +// } +// if (!checkResponseHeaders()) { +// // LOG(Network, "WebSocketHandshake %p readServerHandshake() checkResponseHeaders() failed", this); +// m_mode = Failed; +// return p - header; +// } + +// m_mode = Connected; +// return p - header; +// } + +// WebSocketHandshake::Mode WebSocketHandshake::mode() const +// { +// return m_mode; +// } + +// String WebSocketHandshake::failureReason() const +// { +// return m_failureReason; +// } + +// String WebSocketHandshake::serverWebSocketProtocol() const +// { +// return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SecWebSocketProtocol); +// } + +// String WebSocketHandshake::serverSetCookie() const +// { +// return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SetCookie); +// } + +// String WebSocketHandshake::serverUpgrade() const +// { +// return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::Upgrade); +// } + +// String WebSocketHandshake::serverConnection() const +// { +// return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::Connection); +// } + +// String WebSocketHandshake::serverWebSocketAccept() const +// { +// return m_serverHandshakeResponse.httpHeaderFields().get(HTTPHeaderName::SecWebSocketAccept); +// } + +// void WebSocketHandshake::addExtensionProcessor(std::unique_ptr<WebSocketExtensionProcessor> processor) +// { +// m_extensionDispatcher.addProcessor(WTFMove(processor)); +// } + +// URL WebSocketHandshake::httpURLForAuthenticationAndCookies() const +// { +// URL url = m_url.isolatedCopy(); +// bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http"); +// ASSERT_UNUSED(couldSetProtocol, couldSetProtocol); +// return url; +// } + +// // https://tools.ietf.org/html/rfc6455#section-4.1 +// // "The HTTP version MUST be at least 1.1." +// static inline bool headerHasValidHTTPVersion(StringView httpStatusLine) +// { +// constexpr char preamble[] = "HTTP/"; +// if (!httpStatusLine.startsWith(preamble)) +// return false; + +// // Check that there is a version number which should be at least three characters after "HTTP/" +// unsigned preambleLength = strlen(preamble); +// if (httpStatusLine.length() < preambleLength + 3) +// return false; + +// auto dotPosition = httpStatusLine.find('.', preambleLength); +// if (dotPosition == notFound) +// return false; + +// auto majorVersion = parseInteger<int>(httpStatusLine.substring(preambleLength, dotPosition - preambleLength)); +// if (!majorVersion) +// return false; + +// unsigned minorVersionLength; +// unsigned charactersLeftAfterDotPosition = httpStatusLine.length() - dotPosition; +// for (minorVersionLength = 1; minorVersionLength < charactersLeftAfterDotPosition; minorVersionLength++) { +// if (!isASCIIDigit(httpStatusLine[dotPosition + minorVersionLength])) +// break; +// } +// auto minorVersion = parseInteger<int>(httpStatusLine.substring(dotPosition + 1, minorVersionLength)); +// if (!minorVersion) +// return false; + +// return (*majorVersion >= 1 && *minorVersion >= 1) || *majorVersion >= 2; +// } + +// // Returns the header length (including "\r\n"), or -1 if we have not received enough data yet. +// // If the line is malformed or the status code is not a 3-digit number, +// // statusCode and statusText will be set to -1 and a null string, respectively. +// int WebSocketHandshake::readStatusLine(const uint8_t* header, size_t headerLength, int& statusCode, AtomString& statusText) +// { +// // Arbitrary size limit to prevent the server from sending an unbounded +// // amount of data with no newlines and forcing us to buffer it all. +// static const int maximumLength = 1024; + +// statusCode = -1; +// statusText = nullAtom(); + +// const uint8_t* space1 = nullptr; +// const uint8_t* space2 = nullptr; +// const uint8_t* p; +// size_t consumedLength; + +// for (p = header, consumedLength = 0; consumedLength < headerLength; p++, consumedLength++) { +// if (*p == ' ') { +// if (!space1) +// space1 = p; +// else if (!space2) +// space2 = p; +// } else if (*p == '\0') { +// // The caller isn't prepared to deal with null bytes in status +// // line. WebSockets specification doesn't prohibit this, but HTTP +// // does, so we'll just treat this as an error. +// m_failureReason = "Status line contains embedded null"_s; +// return p + 1 - header; +// } else if (!isASCII(*p)) { +// m_failureReason = "Status line contains non-ASCII character"_s; +// return p + 1 - header; +// } else if (*p == '\n') +// break; +// } +// if (consumedLength == headerLength) +// return -1; // We have not received '\n' yet. + +// auto end = p + 1; +// int lineLength = end - header; +// if (lineLength > maximumLength) { +// m_failureReason = "Status line is too long"_s; +// return maximumLength; +// } + +// // The line must end with "\r\n". +// if (lineLength < 2 || *(end - 2) != '\r') { +// m_failureReason = "Status line does not end with CRLF"_s; +// return lineLength; +// } + +// if (!space1 || !space2) { +// m_failureReason = makeString("No response code found: ", trimInputSample(header, lineLength - 2)); +// return lineLength; +// } + +// StringView httpStatusLine(header, space1 - header); +// if (!headerHasValidHTTPVersion(httpStatusLine)) { +// m_failureReason = makeString("Invalid HTTP version string: ", httpStatusLine); +// return lineLength; +// } + +// StringView statusCodeString(space1 + 1, space2 - space1 - 1); +// if (statusCodeString.length() != 3) // Status code must consist of three digits. +// return lineLength; +// for (int i = 0; i < 3; ++i) { +// if (!isASCIIDigit(statusCodeString[i])) { +// m_failureReason = makeString("Invalid status code: ", statusCodeString); +// return lineLength; +// } +// } + +// statusCode = parseInteger<int>(statusCodeString).value(); +// statusText = AtomString(space2 + 1, end - space2 - 3); // Exclude "\r\n". +// return lineLength; +// } + +// const uint8_t* WebSocketHandshake::readHTTPHeaders(const uint8_t* start, const uint8_t* end) +// { +// StringView name; +// String value; +// bool sawSecWebSocketExtensionsHeaderField = false; +// bool sawSecWebSocketAcceptHeaderField = false; +// bool sawSecWebSocketProtocolHeaderField = false; +// auto p = start; +// for (; p < end; p++) { +// size_t consumedLength = parseHTTPHeader(p, end - p, m_failureReason, name, value); +// if (!consumedLength) +// return nullptr; +// p += consumedLength; + +// // Stop once we consumed an empty line. +// if (name.isEmpty()) +// break; + +// HTTPHeaderName headerName; +// if (!findHTTPHeaderName(name, headerName)) { +// // Evidence in the wild shows that services make use of custom headers in the handshake +// m_serverHandshakeResponse.addUncommonHTTPHeaderField(name.toString(), value); +// continue; +// } + +// // https://tools.ietf.org/html/rfc7230#section-3.2.4 +// // "Newly defined header fields SHOULD limit their field values to US-ASCII octets." +// if ((headerName == HTTPHeaderName::SecWebSocketExtensions +// || headerName == HTTPHeaderName::SecWebSocketAccept +// || headerName == HTTPHeaderName::SecWebSocketProtocol) +// && !value.isAllASCII()) { +// m_failureReason = makeString(name, " header value should only contain ASCII characters"); +// return nullptr; +// } + +// if (headerName == HTTPHeaderName::SecWebSocketExtensions) { +// if (sawSecWebSocketExtensionsHeaderField) { +// m_failureReason = "The Sec-WebSocket-Extensions header must not appear more than once in an HTTP response"_s; +// return nullptr; +// } +// sawSecWebSocketExtensionsHeaderField = true; +// } else { +// if (headerName == HTTPHeaderName::SecWebSocketAccept) { +// if (sawSecWebSocketAcceptHeaderField) { +// m_failureReason = "The Sec-WebSocket-Accept header must not appear more than once in an HTTP response"_s; +// return nullptr; +// } +// sawSecWebSocketAcceptHeaderField = true; +// } else if (headerName == HTTPHeaderName::SecWebSocketProtocol) { +// if (sawSecWebSocketProtocolHeaderField) { +// m_failureReason = "The Sec-WebSocket-Protocol header must not appear more than once in an HTTP response"_s; +// return nullptr; +// } +// sawSecWebSocketProtocolHeaderField = true; +// } + +// m_serverHandshakeResponse.addHTTPHeaderField(headerName, value); +// } +// } +// return p; +// } + +// bool WebSocketHandshake::checkResponseHeaders() +// { +// const String& serverWebSocketProtocol = this->serverWebSocketProtocol(); +// const String& serverUpgrade = this->serverUpgrade(); +// const String& serverConnection = this->serverConnection(); +// const String& serverWebSocketAccept = this->serverWebSocketAccept(); + +// if (serverUpgrade.isNull()) { +// m_failureReason = "Error during WebSocket handshake: 'Upgrade' header is missing"_s; +// return false; +// } +// if (serverConnection.isNull()) { +// m_failureReason = "Error during WebSocket handshake: 'Connection' header is missing"_s; +// return false; +// } +// if (serverWebSocketAccept.isNull()) { +// m_failureReason = "Error during WebSocket handshake: 'Sec-WebSocket-Accept' header is missing"_s; +// return false; +// } + +// if (!equalLettersIgnoringASCIICase(serverUpgrade, "websocket"_s)) { +// m_failureReason = "Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'"_s; +// return false; +// } +// if (!equalLettersIgnoringASCIICase(serverConnection, "upgrade"_s)) { +// m_failureReason = "Error during WebSocket handshake: 'Connection' header value is not 'Upgrade'"_s; +// return false; +// } + +// if (serverWebSocketAccept != m_expectedAccept) { +// m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Accept mismatch"_s; +// return false; +// } +// if (!serverWebSocketProtocol.isNull()) { +// if (m_clientProtocol.isEmpty()) { +// m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"_s; +// return false; +// } +// Vector<String> result = m_clientProtocol.split(StringView { WebSocket::subprotocolSeparator() }); +// if (!result.contains(serverWebSocketProtocol)) { +// m_failureReason = "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"_s; +// return false; +// } +// } +// return true; +// } + +// } // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/WebSocketHandshake.h b/src/javascript/jsc/bindings/webcore/WebSocketHandshake.h new file mode 100644 index 000000000..1cefe2e60 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/WebSocketHandshake.h @@ -0,0 +1,102 @@ +// /* +// * Copyright (C) 2011 Google Inc. All rights reserved. +// * Copyright (C) 2021 Apple Inc. All rights reserved. +// * +// * Redistribution and use in source and binary forms, with or without +// * modification, are permitted provided that the following conditions are +// * met: +// * +// * * Redistributions of source code must retain the above copyright +// * notice, this list of conditions and the following disclaimer. +// * * Redistributions in binary form must reproduce the above +// * copyright notice, this list of conditions and the following disclaimer +// * in the documentation and/or other materials provided with the +// * distribution. +// * * Neither the name of Google Inc. nor the names of its +// * contributors may be used to endorse or promote products derived from +// * this software without specific prior written permission. +// * +// * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// */ + +// #pragma once + +// #include <wtf/URL.h> +// #include <wtf/WeakPtr.h> +// #include <wtf/text/WTFString.h> + +// namespace WebCore { + +// class WebSocketHandshake { + +// public: +// enum Mode { +// Incomplete, +// Normal, +// Failed, +// Connected +// }; +// WebSocketHandshake(const URL&, const String& protocol); +// ~WebSocketHandshake() {} + +// const URL& url() const; +// void setURL(const URL&); +// URL httpURLForAuthenticationAndCookies() const; +// const String host() const; + +// const String& clientProtocol() const; +// void setClientProtocol(const String&); + +// bool secure() const; + +// String clientLocation() const; + +// CString clientHandshakeMessage() const; + +// void reset(); + +// int readServerHandshake(const uint8_t* header, size_t len); +// Mode mode() const; +// String failureReason() const; // Returns a string indicating the reason of failure if mode() == Failed. + +// String serverWebSocketProtocol() const; +// String serverSetCookie() const; +// String serverUpgrade() const; +// String serverConnection() const; +// String serverWebSocketAccept() const; +// String acceptedExtensions() const; + +// // void addExtensionProcessor(std::unique_ptr<WebSocketExtensionProcessor>); + +// static String getExpectedWebSocketAccept(const String& secWebSocketKey); + +// private: +// int readStatusLine(const uint8_t* header, size_t headerLength, int& statusCode, AtomString& statusText); + +// // Reads all headers except for the two predefined ones. +// const uint8_t* readHTTPHeaders(const uint8_t* start, const uint8_t* end); +// void processHeaders(); +// bool checkResponseHeaders(); + +// URL m_url; +// String m_clientProtocol; +// bool m_secure; + +// Mode m_mode; +// String m_failureReason; + +// String m_secWebSocketKey; +// String m_expectedAccept; +// }; + +// } // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/WebSocketIdentifier.h b/src/javascript/jsc/bindings/webcore/WebSocketIdentifier.h new file mode 100644 index 000000000..97884abd8 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/WebSocketIdentifier.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice , this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <wtf/ObjectIdentifier.h> + +namespace WebCore { + +enum WebSocketIdentifierType { }; +using WebSocketIdentifier = ObjectIdentifier<WebSocketIdentifierType>; + +} // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/WebSocketStream.cpp b/src/javascript/jsc/bindings/webcore/WebSocketStream.cpp new file mode 100644 index 000000000..434860854 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/WebSocketStream.cpp @@ -0,0 +1,83 @@ +#include "WebSocketStream.h" +#include <uws/uSockets/src/libusockets.h> + +namespace WebCore { + +template<bool SSL, bool isServer> +WebSocketStreamBase<SSL, isServer>* WebSocketStreamBase<SSL, isServer>::adoptSocket(us_socket_t* socket, ScriptExecutionContext* scriptCtx) +{ + using UserData = WebCore::WebSocket; + + /* Adopting a socket invalidates it, do not rely on it directly to carry any data */ + uWS::WebSocket<SSL, isServer, UserData>* webSocket = (uWS::WebSocket<SSL, isServer, UserData>*)us_socket_context_adopt_socket(SSL, + (us_socket_context_t*)webSocketContext, (us_socket_t*)this, sizeof(WebSocketData) + sizeof(UserData)); + + /* For whatever reason we were corked, update cork to the new socket */ + if (wasCorked) { + webSocket->AsyncSocket<SSL>::corkUnchecked(); + } + + /* Initialize websocket with any moved backpressure intact */ + webSocket->init(perMessageDeflate, compressOptions, std::move(backpressure)); +} + +void WebSocketStreamBase::registerHTTPContext(ScriptExecutionContext* script, us_socket_context_t* ctx, us_loop_t* loop) +{ + if constexpr (!isServer) { + if constexpr (SSL) { + Bun__SecureWebSocketUpgradeClient__register(script->jsGlobalObject(), loop, ctx); + } else { + Bun__WebSocketUpgradeClient__register(script->jsGlobalObject(), loop, ctx); + } + } else { + RELEASE_ASSERT_NOT_REACHED(); + } +} + +template<bool SSL, bool isServer> +uWS::WebSocketContext* WebSocketStreamBase<SSL, isServer>::registerClientContext(ScriptExecutionContext*, us_socket_context_t* parent) +{ + uWS::Loop* loop = uWS::Loop::get(); + uWS::WebSocketContext<SSL, isServer>* ctx = uWS::WebSocketContext<SSL, isServer>::create(loop, parent, nullptr); + auto* opts = ctx->getExt(); + + /* Maximum message size we can receive */ + static unsigned int maxPayloadLength = 128 * 1024 * 1024; + /* 2 minutes timeout is good */ + static unsigned short idleTimeout = 120; + /* 64kb backpressure is probably good */ + static unsigned int maxBackpressure = 64 * 1024; + static bool closeOnBackpressureLimit = false; + /* This one depends on kernel timeouts and is a bad default */ + static bool resetIdleTimeoutOnSend = false; + /* A good default, esp. for newcomers */ + static bool sendPingsAutomatically = true; + /* Maximum socket lifetime in seconds before forced closure (defaults to disabled) */ + static unsigned short maxLifetime = 0; + + opts->maxPayloadLength = maxPayloadLength; + opts->maxBackpressure = maxBackpressure; + opts->closeOnBackpressureLimit = closeOnBackpressureLimit; + opts->resetIdleTimeoutOnSend = resetIdleTimeoutOnSend; + opts->sendPingsAutomatically = sendPingsAutomatically; + // opts->compression = compression; + // TODO: + opts->compression = false; + + opts->openHandler = [](uWS::WebSocket<SSL, isServer, WebCore::WebSocket>* ws) { + auto* webSocket = ws->getUserData(); + webSocket->didOpen(); + }; + + opts->messageHandler = [](uWS::WebSocket<SSL, isServer, WebCore::WebSocket>* ws, std::string_view input, uWS::OpCode opCode) { + auto* webSocket = ws->getUserData(); + webSocket->didReceiveData<uWS::WebSocket<SSL, isServer, WebCore::WebSocket>*>(ws, input.data(), input.length()); + }; + + opts->closeHandler = [](uWS::WebSocket<SSL, isServer, WebCore::WebSocket>* ws, int code, std::string_view message) { + auto* webSocket = ws->getUserData(); + webSocket->didClose<uWS::WebSocket<SSL, isServer, WebCore::WebSocket>*>(ws, code, message.data(), message.length()); + }; +} + +}
\ No newline at end of file diff --git a/src/javascript/jsc/bindings/webcore/WebSocketStream.h b/src/javascript/jsc/bindings/webcore/WebSocketStream.h new file mode 100644 index 000000000..a80c94a85 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/WebSocketStream.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "wtf/text/StringImpl.h" +#include "wtf/text/StringView.h" +#include "wtf/text/WTFString.h" +#include "wtf/URL.h" +#include "wtf/Vector.h" +#include "wtf/Function.h" +#include <uws/src/WebSocketContext.h> + +struct us_socket_context_t; +struct us_socket_t; +struct us_loop_t; + +namespace WebCore { + +enum ClosingHandshakeCompletionStatus { + ClosingHandshakeIncomplete, + ClosingHandshakeComplete +}; + +// This class expects the stream to already be connected & ready to go +template<bool isSSL, bool isServer> +class WebSocketStreamBase final { +public: + using WebSocketImpl = uWS::WebSocket<isSSL, isServer>; + using WebSocketStreamImpl = WebSocketStreamBase<isSSL, isServer>; + using WebSocketContext = uWS::WebSocketContext<isSSL, isServer>; + + ~WebSocketStreamBase(); + void didConnect(); + void didReceiveMessage(String&&); + void didReceiveBinaryData(Vector<uint8_t>&&); + void didReceiveMessageError(String&&); + void didUpdateBufferedAmount(unsigned bufferedAmount); + void didStartClosingHandshake(); + + static WebSocketStreamImpl* adoptSocket(us_socket_t* socket, ScriptExecutionContext* scriptCtx); + static void registerHTTPContext(ScriptExecutionContext*, us_socket_context_t*); + + static WebSocketContext* registerClientContext(ScriptExecutionContext*, us_socket_context_t* parent); + void sendData(const uint8_t* data, size_t length, Function<void(bool)>); + void close(); // Disconnect after all data in buffer are sent. + void disconnect(); + size_t bufferedAmount() const; + + void close(int code, const String& reason); // Start closing handshake. + void fail(String&& reason); + enum CloseEventCode { + CloseEventCodeNotSpecified = -1, + CloseEventCodeNormalClosure = 1000, + CloseEventCodeGoingAway = 1001, + CloseEventCodeProtocolError = 1002, + CloseEventCodeUnsupportedData = 1003, + CloseEventCodeFrameTooLarge = 1004, + CloseEventCodeNoStatusRcvd = 1005, + CloseEventCodeAbnormalClosure = 1006, + CloseEventCodeInvalidFramePayloadData = 1007, + CloseEventCodePolicyViolation = 1008, + CloseEventCodeMessageTooBig = 1009, + CloseEventCodeMandatoryExt = 1010, + CloseEventCodeInternalError = 1011, + CloseEventCodeTLSHandshake = 1015, + CloseEventCodeMinimumUserDefined = 3000, + CloseEventCodeMaximumUserDefined = 4999 + }; + + void didClose(unsigned unhandledBufferedAmount, ClosingHandshakeCompletionStatus, unsigned short code, const String& reason); + void didUpgradeURL(); + + WebSocketStreamBase() + { + } +}; + +using WebSocketStream = WebSocketStreamBase<false, false>; +using SecureWebSocketStream = WebSocketStreamBase<true, false>; +using ServerWebSocketStream = WebSocketStreamBase<false, true>; +using ServerSecureWebSocketStream = WebSocketStreamBase<true, true>; + +} // namespace WebCore |